Java解析Excel(apache-poi)避坑指南

一、背景

大家在日常开发过程中,可能会遇到解析excel文件的需求(xlsx/xls文件),在Java开发过程中,一般常用的是apache-poi工具。

Apache POI 是 Java 平台上的一组开源库,用于读写 Microsoft Office 格式的文件,包括 Excel 文件。它支持读取和写入 Excel 文件的各种操作,功能强大且广泛使用。

Apache POI官网:poi.apache.org/

二、需求描述

解析excel文件,并且筛选部分单元格进行修改,在当前excel中新建一个sheet,将修改后的内容重新写回该sheet中

二、使用

1.引入apache-poi依赖

maven:

xml 复制代码
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi</artifactId>
  <version>5.2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId>
  <version>5.2.5</version>
</dependency>

gradle:

xml 复制代码
implementation group: 'org.apache.poi', name: 'poi', version: '5.2.5'
implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '5.2.5'

2.调用poi解析excel

2.1.踩坑示例

java 复制代码
public class ExcelParser {

    public static void main(String[] args) {
        String path = "example.xlsx";  // excel文件绝对路径
        LinkedList<Row> rows = parseExcel(path);
        updateRows(rows);
        // 新建一个sheet,将修改后的Rows写入该sheet
        writeRows(rows, path);
    }
    
    // 解析excel获取Rows
    private static LinkedList<Row> parseExcel(String path) {
        System.out.println("正在解析excel...");
        LinkedList<Row> res = new LinkedList<>();
        try {
                // 读取Excel文件
                FileInputStream fis = new FileInputStream(path);
                Workbook workbook = new XSSFWorkbook(fis);
                Sheet datatypeSheet = workbook.getSheetAt(0);
                int rowNum = datatypeSheet.getPhysicalNumberOfRows();
                for (int i=1; i<rowNum;i++) {
                    res.add(datatypeSheet.getRow(i));
                }
                // 关闭文件流
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        return res;
    }
    
    // 按需求修改Row
    private static void updateRows(LinkedList<Row> rows) {
        // ....
        // 此处直接在原Row对象基础上进行了修改
    }
    
    private static void writeRows(LinkedList<Row> rows, String path) {
        try {
            FileInputStream excelFile = new FileInputStream(path);
            Workbook workbook = WorkbookFactory.create(excelFile); // 直接new XSSFWorkbook也ok
            // 新建sheet
            Sheet newSheet = workbook.createSheet("newSheet");
            // 将修改后的Rows写入该sheet
            for(int i=0;i<rows.size();i++) {
                Row row = rows.get(i);
                Row newRow = newSheet.createRow(i);
                // 此处注意,row.getLastCellNum() 若单元格为空,也会计数!
                for (int j=0;j<row.getLastCellNum();j++) {
                    Cell cell = row.getCell(j);
                    // 此处注意,修改后的cell若为空,那么其cell==null,需要加入空字符串,否则列会乱
                    String value = cell != null ? cell.getStringCellValue() : "";
                    newRow.createCell(j).setCellValue(value);
                }
            }
            // 保存工作簿到当前的Excel文件中
            FileOutputStream fileOut = new FileOutputStream(path);
            workbook.write(fileOut);
            System.out.println("数据添加并保存成功!");
    
            // 关闭文件流
            excelFile.close();
            fileOut.close();
        } catch (IOException | InvalidFormatException e) {
            e.printStackTrace();
        }
    }
}

大家读了上述代码,是不是感觉可以了,应该没什么问题吧!步骤清晰,方法分工明确,简直完美!

但是!在运行过程中会报RuntimeException!

java 复制代码
Exception in thread "main" org.apache.poi.ooxml.POIXMLException: java.io.EOFException: Unexpected end of ZLIB input stream

啊?why?!看着一点问题没有呀,那出现问题了,就解决问题哇,直接搞点靠谱的:

哦?看情况大概率是文件格式有问题了呀,文件格式怎么会有问题呢,后缀也没改呀,是.xlsx,没错呀,那就只能debug了,这里就不详细展示debug的过程了,最终通过n步debug发现,在writeRows方法对Rows写入时,导致文件格式有问题了,是什么底层原因导致的呢?

结果显示,当你解析一个Excel获取了一次Workbook1,然后拿到了Row1,再次获取WorkBook2时,如果你想把Row1写入Workbook2,这时会报错!

所以,当写入Excel时,仍然用原来的Workbook即可避免文件格式错误,详细代码如下:

2.2.避坑示例

java 复制代码
public class ExcelParser {

    public static void main(String[] args) {
        String path = "example.xlsx";  // excel文件绝对路径
        Workbook workbook = getWorkbook(path);
        LinkedList<Row> rows = parseExcel(workbook);
        updateRows(rows);
        // 写rows时 将原来的workbook传入,在workbook基础上创建sheet
        writeRows(workbook, rows, path);
    }
    
    // 解析excel获取workbook
    private static Workbook getWorkbook(String path) {
        File file = new File(path);
        FileInputStream excelFile = null;
        Workbook workbook = null;
        try {
            excelFile = new FileInputStream(path);
            workbook = new XSSFWorkbook(excelFile);
            excelFile.close();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return workbook;
    }
    
    // 通过workbook获取Rows
    private static LinkedList<Row> parseExcel(Workbook workbook) {
        System.out.println("正在解析excel...");
        LinkedList<Row> res = new LinkedList<>();
        // 读取Excel文件
        Sheet datatypeSheet = workbook.getSheetAt(0);
        int rowNum = datatypeSheet.getPhysicalNumberOfRows();
        for (int i=1; i<rowNum;i++) {
            res.add(datatypeSheet.getRow(i));
        }
        return res;
    }
    
    // 按需求修改Row
    private static void updateRows(LinkedList<Row> rows) {
        // ....
        // 此处直接在原Row对象基础上进行了修改
    }
    
    private static void writeRows(Workbook, workbook, LinkedList<Row> rows, String path) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(path);
            // 此处注意,在原workbook上新建sheet
            Sheet newSheet = workbook.createSheet("newSheet");
            // 将修改后的Rows写入该sheet
            for(int i=0;i<rows.size();i++) {
                Row row = rows.get(i);
                Row newRow = newSheet.createRow(i);
                // 此处注意,row.getLastCellNum() 若单元格为空,也会计数!
                for (int j=0;j<row.getLastCellNum();j++) {
                    Cell cell = row.getCell(j);
                    // 此处注意,修改后的cell若为空,那么其cell==null,需要加入空字符串,否则列会乱
                    String value = cell != null ? cell.getStringCellValue() : "";
                    newRow.createCell(j).setCellValue(value);
                }
            }
            // 保存工作簿到当前的Excel文件中
            // 此处注意,新sheet中添加数据后,将原workbook写入输出流
            workbook.write(fos);
            System.out.println("数据添加并保存成功!");
    
            // 关闭文件流
            fileOut.close();
        } catch (IOException | InvalidFormatException e) {
            e.printStackTrace();
        }
    }
}

以上方法可以避免出现文件格式错误的情况,以上方法可以避免出现文件格式错误的情况,需要特别注意的地方是代码注释中标注"此处注意"字样的部分,大家可以直接 ctrl+f 搜索。

至于poi底层是怎么实现的,导致了文件格式错误问题,笔者没有研究,希望各位有经验的大佬评论区指导指导~

2.3.建议

由于Excel现在功能逐渐强大,其内部可以嵌入各种宏函数,可以做很多骚操作,这样我们在解析Excel时就难免会遇到各种莫名其妙的问题,一般情况大家解析的Excel都是简单的文本数据,所以说,大家可以动动手,先把Excel转换为.csv格式的表格,这种格式其存储的都是简单的文本,可以避免很多摸不着头脑的坑。

相关推荐
Theodore_10222 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸3 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象4 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了4 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
小二·4 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
懒洋洋大魔王5 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq
武子康5 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神5 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
qq_327342736 小时前
Java实现离线身份证号码OCR识别
java·开发语言