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

相关推荐
Mryan200535 分钟前
解决GraalVM Native Maven Plugin错误:JAVA_HOME未指向GraalVM Distribution
java·开发语言·spring boot·maven
VX_CXsjNo11 小时前
免费送源码:Java+SSM+Android Studio 基于Android Studio游戏搜索app的设计与实现 计算机毕业设计原创定制
java·spring boot·spring·游戏·eclipse·android studio·android-studio
ylfhpy1 小时前
Java面试黄金宝典33
java·开发语言·数据结构·面试·职场和发展·排序算法
乘风!1 小时前
Java导出excel,表格插入pdf附件,以及实现过程中遇见的坑
java·pdf·excel
小小鸭程序员1 小时前
Vue组件化开发深度解析:Element UI与Ant Design Vue对比实践
java·vue.js·spring·ui·elementui
南宫生2 小时前
Java迭代器【设计模式之迭代器模式】
java·学习·设计模式·kotlin·迭代器模式
seabirdssss2 小时前
通过动态获取项目的上下文路径来确保请求的 URL 兼容两种启动方式(IDEA 启动和 Tomcat 部署)下都能正确解析
java·okhttp·tomcat·intellij-idea
kill bert3 小时前
第30周Java分布式入门 消息队列 RabbitMQ
java·分布式·java-rabbitmq
穿林鸟3 小时前
Spring Boot项目信创国产化适配指南
java·spring boot·后端
此木|西贝4 小时前
【设计模式】模板方法模式
java·设计模式·模板方法模式