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格式的表格,这种格式其存储的都是简单的文本,可以避免很多摸不着头脑的坑。

相关推荐
loveLifeLoveCoding16 分钟前
Java List sort() 排序
java·开发语言
草履虫·22 分钟前
【Java集合】LinkedList
java
AngeliaXue24 分钟前
Java集合(List篇)
java·开发语言·list·集合
世俗ˊ25 分钟前
Java中ArrayList和LinkedList的比较
java·开发语言
zhouyiddd30 分钟前
Maven Helper 插件
java·maven·intellij idea
攸攸太上38 分钟前
Docker学习
java·网络·学习·docker·容器
Milo_K1 小时前
项目文件配置
java·开发语言
程序员大金1 小时前
基于SpringBoot+Vue+MySQL的养老院管理系统
java·vue.js·spring boot·vscode·后端·mysql·vim
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS网上购物商城(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
nsa652231 小时前
Knife4j 一款基于Swagger的开源文档管理工具
java