一、背景
大家在日常开发过程中,可能会遇到解析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格式的表格,这种格式其存储的都是简单的文本,可以避免很多摸不着头脑的坑。