引言
java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
在做技术选择的时候,发现国内个人开发的eec与阿里开源的easyexcel有一比之力。
Easyexcel与EEC比较
1. Easyexcel
宗旨—— 快速、简单避免OOM的java处理Excel工具。
Easyexcel是阿里巴巴的一个开源项目,权威性不用多说,号称64M内存1分钟内读取75M(46W行25列)的Excel,并且急速模式能更快(代价就是会耗费更多的内存)。
easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便。
背靠阿里,知名度高,目前国内使用较多。
2. EEC
宗旨 —— 一个高效、低内存支持GB级别Excel读写工具,非POI底层支持流式读取。
EEC(Excel Export Core)是一个Excel读取和写入工具,目前支持xlsx格式的读取、写入以及xls格式的读取(xls支持版本BIFF8也就是excel 97~2003格式)。
与传统Excel操作工具不同之处在于EEC并不缓存或只少量缓存数据到内存,写文件时EEC使用分片来处理较大的数据,使用迭代模式读取Excel行内容,当你使用某行数据的时才去解析它们,而不会将整个文件读入到内存。
EEC最大特点是高速和低内存,如果在项目中做数据导入导出,选用EEC将为你带来极大的便利,同时它的可扩展能力也不弱。
EEC采用单线程、高IO设计,所以多核心、高内存并不能显著提高速度,高主频和一块好SSD能显著提升速度。
EEC并不是一个功能全面的Excel操作工具类,它功能有限并不能用它来完全替代Apache POI,它最擅长的操作是表格处理。比如将数据库表导出为Excel或者读取Excel表格内容到Stream或数据库。
实战
pom.xml主要依赖如下:
com.alibaba
easyexcel
1.1.2-beat1
org.ttzero
eec
0.4.6
org.projectlombok
lombok
1.18.2
1. easyexcel读取和写入Excel
package Excel;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.support.ExcelTypeEnum;
/**
* 使用阿里巴巴开源的 easyexcel 进行Excel的读取与写入
* jar包版本为 1.1.2-beat1
* 相对 Apache poi、jxl能够加快速度并节省内存
*/
public class ExcelReadAndWrite {
private static String input = "src/main/java/Excel/easyexcel.xlsx";
private static String output = "src/main/java/Excel/easyexcel.xlsx";
/**
* 读取 Excel
*
* @throws Exception
*/
public static void read() {
try (InputStream in = new FileInputStream(input)) {
AnalysisEventListener> listener = new AnalysisEventListener>() {
@Override
public void invoke(List object, AnalysisContext context) {
// 打印每行数据
System.out.println(object);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
};
ExcelReader excelReader = EasyExcelFactory.getReader(in, listener);
excelReader.read();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 写入Excel,支持 XLS 和 XLSX 两种格式
*
* @throws IOException
*/
public static long write() {
long start = 0;
try (OutputStream out = new FileOutputStream(output);) {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("sheet1");
List> data = new ArrayList<>();
// 生成待写入的数据
for (int i = 0; i < 20 * 10000; i++) {
List item = new ArrayList<>();
for (int j = 0; j < 7; j++) {
item.add("Hello World!");
}
data.add(item);
}
// 添加表头
// List> head = new ArrayList<>();
// List headCoulumn1 = new ArrayList<>();
// headCoulumn1.add("第一列");
// head.add(headCoulumn1);
// Table table = new Table(1);
// table.setHead(head);
start = System.currentTimeMillis();
// 写入到文件
writer.write0(data, sheet1);
writer.finish();
} catch (IOException e) {
e.printStackTrace();
}
return start;
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
read();
long end = System.currentTimeMillis();
System.out.println("读取耗时" + (end - start) + "ms");
start = write();
end = System.currentTimeMillis();
System.out.println("写入耗时" + (end - start) + "ms");
}
}
2. eec读取和写入excel
相比easyexcel来说,eec在内存和速度上都是占优势的,但由于是个人开发的,可能测试覆盖率并不是很高,也许有未知BUG。
package Excel;
import lombok.Data;
import org.ttzero.excel.entity.ListSheet;
import org.ttzero.excel.entity.Workbook;
import org.ttzero.excel.reader.ExcelReader;
import org.ttzero.excel.reader.Row;
import org.ttzero.excel.reader.Sheet;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* EEC读取与写入Excel
* 参考
* https://www.ttzero.org/excel/2020/03/03/eec-vs-easyexcel.html EEC vs easyexcel
*/
@Data
class SelfData {
private String a;
private String b;
private String c;
private String d;
private String e;
private String f;
private String g;
}
public class EECOpe {
private static String input = "src/main/java/Excel/Large_EEC.xlsx";
private static String output = "src/main/java/Excel";
/**
* 使用iterator遍历所有行
*/
public static void read() {
try (ExcelReader reader = ExcelReader.read(Paths.get(input))) {
Sheet sheet = reader.sheet(0);
Iterator ite = sheet.iterator();
// 迭代遍历
while (ite.hasNext()) {
System.out.println(ite.next());
}
// 流式处理
// reader.sheets().flatMap(Sheet::rows).forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
public static List data() {
List list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
SelfData largeData = new SelfData();
list.add(largeData);
largeData.setA("Hello World");
largeData.setB("Hello World");
largeData.setC("Hello World");
largeData.setD("Hello World");
largeData.setE("Hello World");
largeData.setF("Hello World");
largeData.setG("Hello World");
}
return list;
}
public static long write() {
long start = System.currentTimeMillis();
try {
new Workbook("Large_EEC").addSheet(new ListSheet() {
int n = 0;
@Override
public List more() {
return n++ < 200 ? data() : null;
}
}).writeTo(Paths.get(output));
} catch (IOException e) {
e.printStackTrace();
}
return start;
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
System.out.println(start);
read();
long end = System.currentTimeMillis();
System.out.println("读取耗时" + (end - start) + "ms");
start = write();
end = System.currentTimeMillis();
System.out.println("写入耗时" + (end - start) + "ms");
}
}
若读取出来后不进行打印,总体时间还会进一步缩短。
性能方面,有人测试的结果是 ——读和写EEC都是easyexcel的一到两倍。
小结
总的来说,对内存和速度有一定要求并且求稳,就使用阿里巴巴开源的easyexcel。若对速度有更高的要求,则选择EEC。
参考