刚入职第一个需求
这是入职做的第一个需求,系统中有一个拼接excel的功能(第一个excel和第二个excel横向拼接成一个新的excel,再继续和下一个excel拼接成一个更大的excel)。通常处理excel会使用jxl或者poi,poi支持excel2007,jxl只支持到excel2003(即以.xls结尾的excel),所以我们选择使用poi来处理excel,但是poi有个很大的缺陷,常用的用户模式内存占用过高,一个3M的excel经过poi读取之后会占用600m甚至更大的内存,所以就有了这个需求:优化内存占用。
思路
poi官网上提供了三种读写excel的方式
eventmodel即sax模式,cpu和内存占用低,但是只能读,不能写。usermodel即将excel解析成dom的方式,这种方式是最常用的,但是cpu和内存占用很高。sxssf即流式写的方式,cpu和内存占用低,但是只能写,不能读。
看过这三种方式之后,很容易想到使用sax模式来读,使用流式写来写。
细节
首先是实现sax模式,sax模式除了依赖poi相关的包之外,还依赖xmlReader相关的包(xlsx格式的excel是通过xml来存储数据的,sax模式的本质是解析xml),导入包后,需要继承DefaultHandler,重写startElement,endElement,characters三个回调方法。startElement方法用于解析到xml的开标签的时候执行,endElement方法用于解析到闭标签的时候执行,characters用于获取标签中间的数据,也就是单元格中的数据。
<!-- https://mvnrepository.com/artifact/xerces/xercesImpl -->
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.0</version>
</dependency>
private static class ExcelDataHandler extends DefaultHandler {
private SharedStringsTable sst;
private List<List<Object>> data;
private List<Object> currentRow;
private Object lastContent;
private int lastRow;
private int lastCol;
private boolean sstRef; // 是否是sst的索引
private boolean numValue; // 是否是数字
public ExcelDataHandler(SharedStringsTable sst) {
this.sst = sst;
this.data = new ArrayList<>();
lastCol = lastRow = -1;
}
public List<List<Object>> getData(</