讲师中心 微信公众号
AI工具推荐 视频效率加速

读取Excel数据并保持列顺序的Java实践

酷杰酱_3431

酷杰酱_3431

发布时间:2025-07-21 21:26:01

|

468人浏览过

|

来源于php中文网

原创

读取Excel数据并保持列顺序的Java实践

本文旨在解决使用Java读取Excel数据并存储到List<Map<String, String>>时,Map中列顺序混乱的问题。核心解决方案是利用LinkedHashMap来替代默认的HashMap,从而确保数据在Map中保持与Excel源文件一致的插入顺序,便于后续处理或写回Excel。文章将提供详细的代码示例和解释,帮助开发者实现有序的Excel数据处理。

1. 问题背景:HashMap的无序性

在java中,java.util.hashmap是一种常用的键值对存储结构。然而,hashmap的内部实现是基于哈希表,它不保证元素的迭代顺序。这意味着当你将excel表格中的列名和值存入hashmap时,即使你按照从左到右的顺序插入,hashmap在迭代时也可能以任意顺序返回这些键值对。这对于需要严格保持列顺序的场景(如将数据写回excel或按原顺序处理数据)来说,是一个显著的问题。

例如,一个Excel表格的列顺序是 column1, column2:

column1    column2
value1      value2
value3      value4

如果使用HashMap存储,得到的Map可能呈现如下无序状态:

0 = "column2" -> value2
    "column1" -> value1
1 = "column2" -> value4
    "column1" -> value3

这与我们期望的 column1 -> value1, column2 -> value2 的顺序不符。

2. 解决方案:使用LinkedHashMap保持插入顺序

为了解决HashMap的无序性问题,Java提供了java.util.LinkedHashMap。LinkedHashMap继承自HashMap,并额外维护了一个双向链表,用于记录元素的插入顺序。因此,当你遍历LinkedHashMap时,它会按照键值对被插入的顺序返回它们。这正是我们读取Excel数据并希望保持列顺序所需的特性。

立即学习Java免费学习笔记(深入)”;

Apache Superset Dashboard and SQL Exploration Skill
Apache Superset Dashboard and SQL Exploration Skill

Apache Superset 是一个广泛采用的开源 BI 平台,用于 SQL 探索、图表构建和仪表板交付。当代理需要查询仓库数据、组装仪表板或使用成熟的分析界面解释指标而不是临时笔记本代码时,此技能非常有用。

下载

3. 代码实现与优化

以下是修改后的readExcelSheet方法,它将HashMap替换为LinkedHashMap,以确保列的顺序得到保留。

import org.apache.poi.ss.usermodel.*;
import java.util.*;

public class ExcelReaderUtil {

    /**
     * 从Excel工作表中读取数据,并以有序的List<Map<String, String>>形式返回。
     * 每个Map代表一行数据,Map中的键值对顺序与Excel列的插入顺序一致。
     *
     * @param sheet 要读取的Excel工作表对象
     * @return 包含Excel数据的List,如果工作表为空则返回空列表
     */
    public static List<Map<String, String>> readExcelSheet(Sheet sheet) {
        // 获取行的迭代器
        Iterator<Row> rows = sheet.iterator();

        // 如果没有行,则返回空列表
        if (!rows.hasNext()) {
            return Collections.emptyList();
        }

        // 读取表头(第一行)作为Map的键
        Row header = rows.next();
        List<String> keys = new ArrayList<>();
        // 遍历表头单元格,获取列名
        for (Cell cell : header) {
            String value = getCellValueAsString(cell); // 使用辅助方法获取单元格值
            if (!value.isEmpty()) {
                keys.add(value);
            } else {
                // 遇到空列名时,可以根据实际需求选择跳出或继续
                // 这里选择跳出,认为后续列可能不再是有效表头
                break;
            }
        }

        // 初始化结果列表
        List<Map<String, String>> result = new ArrayList<>();

        // 遍历剩余的每一行数据
        while (rows.hasNext()) {
            Row row = rows.next();
            // 使用LinkedHashMap来保证列的插入顺序
            Map<String, String> rowMap = new LinkedHashMap<>();

            // 遍历表头键,按顺序填充当前行的数据
            for (int i = 0; i < keys.size(); ++i) {
                // 获取单元格,如果不存在则创建为空白单元格
                Cell cell = row.getCell(i, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
                String value = getCellValueAsString(cell); // 使用辅助方法获取单元格值
                rowMap.put(keys.get(i), value);
            }

            // 只有当行不为空时才添加到结果列表
            // 判断行是否为空:检查Map中所有值是否都为空字符串
            if (!rowMap.values().stream().allMatch(String::isEmpty)) {
                result.add(rowMap);
            }
        }

        return result;
    }

    /**
     * 辅助方法:安全地获取单元格的字符串值,处理不同类型的单元格。
     *
     * @param cell 单元格对象
     * @return 单元格的字符串表示,如果单元格为null或空白,则返回空字符串
     */
    private static String getCellValueAsString(Cell cell) {
        if (cell == null) {
            return "";
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                // 对于日期类型,需要额外处理,这里简化为数值
                if (DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue().toString(); // 或者格式化为特定日期字符串
                } else {
                    return String.valueOf(cell.getNumericCellValue());
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                // 对于公式单元格,可以尝试获取计算后的值
                try {
                    return String.valueOf(cell.getNumericCellValue()); // 尝试获取数值结果
                } catch (IllegalStateException e) {
                    try {
                        return cell.getStringCellValue(); // 尝试获取字符串结果
                    } catch (IllegalStateException ex) {
                        return ""; // 无法获取值
                    }
                }
            case BLANK:
                return "";
            default:
                return ""; // 默认返回空字符串
        }
    }

    // 示例用法 (需要Apache POI库)
    public static void main(String[] args) throws Exception {
        // 假设有一个名为 "example.xlsx" 的Excel文件
        // 创建一个模拟的Workbook和Sheet用于测试
        Workbook workbook = new org.apache.poi.xssf.usermodel.XSSFWorkbook();
        Sheet sheet = workbook.createSheet("Sheet1");

        // 创建表头
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("column1");
        headerRow.createCell(1).setCellValue("column2");
        headerRow.createCell(2).setCellValue("column3"); // 增加一列测试

        // 创建数据行1
        Row dataRow1 = sheet.createRow(1);
        dataRow1.createCell(0).setCellValue("value1");
        dataRow1.createCell(1).setCellValue("value2");
        dataRow1.createCell(2).setCellValue(123); // 测试数值类型

        // 创建数据行2
        Row dataRow2 = sheet.createRow(2);
        dataRow2.createCell(0).setCellValue("value3");
        dataRow2.createCell(1).setCellValue("value4");
        dataRow2.createCell(2).setCellValue(true); // 测试布尔类型

        // 创建空行(应被过滤)
        sheet.createRow(3);

        // 调用读取方法
        List<Map<String, String>> data = readExcelSheet(sheet);

        // 打印结果,观察列顺序
        for (Map<String, String> rowMap : data) {
            System.out.println("--- Row ---");
            rowMap.forEach((key, value) -> System.out.println("  " + key + " -> " + value));
        }

        workbook.close();
    }
}

代码改进说明:

  1. LinkedHashMap的使用: 最核心的改动是将 Map<String, String> rowMap = new HashMap<>(); 替换为 Map<String, String> rowMap = new LinkedHashMap<>();。这确保了在将列名和值放入rowMap时,它们会按照插入的顺序(即Excel中从左到右的列顺序)进行存储。
  2. getCellValueAsString辅助方法: 原始代码中直接使用 cell.toString() 来获取单元格值,这可能导致非字符串类型的单元格(如数值、日期、布尔值)在转换时出现问题或不符合预期。新增的getCellValueAsString辅助方法根据单元格类型安全地获取其字符串表示,提高了代码的健壮性。
  3. 空行判断优化: if (!rowMap.values().stream().allMatch(String::isEmpty)) 能够有效过滤掉所有单元格都为空的行。

4. 其他Map类型选择

除了LinkedHashMap,Java还提供了其他Map实现,它们在特定场景下也可能有用:

  • TreeMap: TreeMap实现了SortedMap接口,它会根据键的自然顺序(或自定义的比较器)对键进行排序。如果你希望Excel列数据按照列名的字母顺序(或数字顺序)而不是原始插入顺序进行存储,那么TreeMap可能是一个选择。然而,对于保留原始Excel列顺序的需求,LinkedHashMap是更直接和合适的方案。

5. 注意事项与总结

  • 依赖管理: 上述代码使用了Apache POI库来处理Excel文件。请确保你的项目中已添加相应的Maven或Gradle依赖,例如:
    <!-- Maven -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.3</version> <!-- 使用最新稳定版本 -->
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.3</version> <!-- 处理.xlsx文件需要此依赖 -->
    </dependency>
  • 单元格类型处理: getCellValueAsString方法提供了一个基本的单元格类型处理示例。在实际应用中,你可能需要更精细地处理日期格式、公式求值错误等情况,以满足具体的业务需求。
  • 性能考量: 对于非常大的Excel文件,一次性将所有数据加载到内存中的List<Map>可能会消耗大量内存。在这种情况下,可以考虑流式处理数据,或者分批处理。
  • 空列名处理: 示例代码在遇到空列名时会break跳出表头读取。如果你的Excel文件可能存在中间有空列名但后续仍有有效列名的情况,你可能需要调整此逻辑,例如继续读取所有单元格直到行尾。

通过将HashMap替换为LinkedHashMap,可以有效地解决在Java中读取Excel数据时列顺序混乱的问题,确保数据在内存中保持与源文件一致的结构,从而简化后续的数据处理和回写操作。

热门AI工具

更多
VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

LibLibAI
LibLibAI Hot

一款AI视频创作工具,主要用于国内领先的AI创意平台,以海量模型、低门槛操作与“创作-分享-商业化”生态,让小白与专业创作者都能高效实现图文乃至视频创意表达,适合需要提升相关任务效率的用户。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

相关专题

更多
Java Maven专题
Java Maven专题

本专题聚焦 Java 主流构建工具 Maven 的学习与应用,系统讲解项目结构、依赖管理、插件使用、生命周期与多模块项目配置。通过企业管理系统、Web 应用与微服务项目实战,帮助学员全面掌握 Maven 在 Java 项目构建与团队协作中的核心技能。

2780

2025.09.15

Java Maven/Gradle 构建与依赖管理合集
Java Maven/Gradle 构建与依赖管理合集

系统讲解 Java 项目构建工具的使用与进阶配置,涵盖 Maven 的 POM 文件结构、生命周期(clean/compile/package/install/deploy)与插件机制、依赖范围(compile/provided/test/runtime)与传递依赖管理、多模块聚合与继承、私有 Nexus 仓库发布,以及 Gradle 的 Groovy / Kotlin DSL 语法、Task 自定义与增量构建、依赖版本目录(Versi

289

2026.05.09

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

5059

2023.08.02

java中break的作用
java中break的作用

本专题整合了java中break的用法教程,阅读专题下面的文章了解更多详细内容。

2080

2025.10.15

java break和continue
java break和continue

本专题整合了java break和continue的区别相关内容,阅读专题下面的文章了解更多详细内容。

647

2025.10.24

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

1518

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

2184

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

5684

2023.10.24

Vibeknow在线使用入口合集
Vibeknow在线使用入口合集

本专题汇总了Vibeknow在线创作视频的官方入口及网页版使用教程,涵盖PPT、PDF、Word等文档一键转讲解视频的核心操作,并整理了免费版水印规则与手机端浏览器访问指南,助你快速将知识内容视频化。

0

2026.09.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Apache Maven 官方安装指南
Apache Maven 官方安装指南

共0课时 | 0人学习

Apache Maven 官方用户中心
Apache Maven 官方用户中心

共0课时 | 0人学习

Apache Subversion 官方手册
Apache Subversion 官方手册

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn