首页 > Java > java教程 > 正文

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

聖光之護
发布: 2025-07-21 21:26:01
原创
440人浏览过

读取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免费学习笔记(深入)”;

Softr Studio
Softr Studio

最简单的无代码web开发平台

Softr Studio 55
查看详情 Softr Studio

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数据时列顺序混乱的问题,确保数据在内存中保持与源文件一致的结构,从而简化后续的数据处理和回写操作。

以上就是读取Excel数据并保持列顺序的Java实践的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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