0

0

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

聖光之護

聖光之護

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

|

448人浏览过

|

来源于php中文网

原创

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

本文旨在解决使用Java读取Excel数据并存储到List>时,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免费学习笔记(深入)”;

Text-To-Song
Text-To-Song

免费的实时语音转换器和调制器

下载

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 rowMap = new HashMap(); 替换为 Map 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可能会消耗大量内存。在这种情况下,可以考虑流式处理数据,或者分批处理。
  • 空列名处理: 示例代码在遇到空列名时会break跳出表头读取。如果你的Excel文件可能存在中间有空列名但后续仍有有效列名的情况,你可能需要调整此逻辑,例如继续读取所有单元格直到行尾。

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Java Maven专题
Java Maven专题

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

0

2025.09.15

string转int
string转int

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

1010

2023.08.02

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

846

2023.08.22

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

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

120

2025.10.15

java break和continue
java break和continue

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

261

2025.10.24

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

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

760

2023.08.03

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

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

221

2023.09.04

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

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

1566

2023.10.24

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
RunnerGo从入门到精通
RunnerGo从入门到精通

共22课时 | 1.8万人学习

尚学堂Mahout视频教程
尚学堂Mahout视频教程

共18课时 | 3.3万人学习

Linux优化视频教程
Linux优化视频教程

共14课时 | 3.2万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号