Java解析excel工具easyexcel助你快速簡單避免OOM[圖]
Java解析、生成Excel比較有名的框架有Apache poi、jxl。但他們都存在一個嚴重的問題就是很是的耗內存,poi有一套SAX模式的API能夠必定程度的解決一些內存溢出的問題,但POI仍是有一些缺陷,好比07版Excel解壓縮以及解壓後存儲都是在內存中完成的,內存消耗依然很大。easyexcel重寫了poi對07版Excel的解析,可以本來一個3M的excel用POI sax依然須要100M左右內存下降到KB級別,而且再大的excel不會出現內存溢出,03版依賴POI的sax模式。在上層作了模型轉換的封裝,讓使用者更加簡單方便。html
easyexcel核心功能
讀任意大小的0三、07版Excel不會OOM
讀Excel自動經過註解,把結果映射爲java模型
讀Excel支持多sheet
讀Excel時候是否對Excel內容作trim()增長容錯
寫小量數據的03版Excel(不要超過2000行)
寫任意大07版Excel不會OOM
寫Excel經過註解將表頭自動寫入Excel
寫Excel能夠自定義Excel樣式 如:字體,加粗,表頭顏色,數據內容顏色
寫Excel到多個不一樣sheet
寫Excel時一個sheet能夠寫多個Table
寫Excel時候自定義是否須要寫表頭
快速使用
1. JAR包依賴
使用前最好諮詢下最新版,或者到mvn倉庫搜索一下easyexcel的最新版
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>{latestVersion}</version>
</dependency>
2. 讀取Excel
使用easyexcel解析0三、07版本的Excel只是ExcelTypeEnum不一樣,其餘使用徹底相同,使用者無需知道底層解析的差別。
無java模型直接把excel解析的每行結果以List返回 在ExcelListener獲取解析結果
讀excel代碼示例以下:
@Test
public void testExcel2003NoModel() {
InputStream inputStream = getInputStream("loan1.xls");
try {
// 解析每行結果在listener中處理
ExcelListener listener = new ExcelListener();
ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLS, null, listener);
excelReader.read();
} catch (Exception e) {
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ExcelListener示例代碼以下:
/* 解析監聽器,
* 每解析一行會回調invoke()方法。
* 整個excel解析結束會執行doAfterAllAnalysed()方法
*
* 下面只是我寫的一個樣例而已,能夠根據本身的邏輯修改該類。
* @author jipengfei
* @date 2017/03/14
*/
public class ExcelListener extends AnalysisEventListener {
//自定義用於暫時存儲data。
//能夠經過實例獲取該值
private List<Object> datas = new ArrayList<Object>();
public void invoke(Object object, AnalysisContext context) {
System.out.println("當前行:"+context.getCurrentRowNum());
System.out.println(object);
datas.add(object);//數據存儲到list,供批量處理,或後續本身業務邏輯處理。
doSomething(object);//根據本身業務作處理
}
private void doSomething(Object object) {
//一、入庫調用接口
}
public void doAfterAllAnalysed(AnalysisContext context) {
// datas.clear();//解析結束銷燬不用的資源
}
public List<Object> getDatas() {
return datas;
}
public void setDatas(List<Object> datas) {
this.datas = datas;
}
}
有java模型映射
java模型寫法以下:
public class LoanInfo extends BaseRowModel {
@ExcelProperty(index = 0)
private String bankLoanId;
@ExcelProperty(index = 1)
private Long customerId;
@ExcelProperty(index = 2,format = "yyyy/MM/dd")
private Date loanDate;
@ExcelProperty(index = 3)
private BigDecimal quota;
@ExcelProperty(index = 4)
private String bankInterestRate;
@ExcelProperty(index = 5)
private Integer loanTerm;
@ExcelProperty(index = 6,format = "yyyy/MM/dd")
private Date loanEndDate;
@ExcelProperty(index = 7)
private BigDecimal interestPerMonth;
@ExcelProperty(value = {"一級表頭","二級表頭"})
private BigDecimal sax;
}
@ExcelProperty(index = 3)數字表明該字段與excel對應列號作映射,也能夠採用 @ExcelProperty(value = {「一級表頭」,」二級表頭」})用於解決不確切知道excel第幾列和該字段映射,位置不固定,但表頭的內容知道的狀況。
@Test
public void testExcel2003WithReflectModel() {
InputStream inputStream = getInputStream("loan1.xls");
try {
// 解析每行結果在listener中處理
AnalysisEventListener listener = new ExcelListener();
ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLS, null, listener);
excelReader.read(new Sheet(1, 2, LoanInfo.class));
} catch (Exception e) {
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
帶模型解析與不帶模型解析主要在構造new Sheet(1, 2, LoanInfo.class)時候包含class。Class須要繼承BaseRowModel暫時BaseRowModel沒有任何內容,後面升級可能會增長一些默認的數據。
3. 生成Excel
每行數據是List無表頭
OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false);
//寫第一個sheet, sheet1 數據全是List<String> 無模型映射關係
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("第一個sheet");
writer.write(getListString(), sheet1);
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
每行數據是一個java模型有表頭—-表頭層級爲一
生成Excel格式以下圖:
模型寫法以下:
public class ExcelPropertyIndexModel extends BaseRowModel {
@ExcelProperty(value = "姓名" ,index = 0)
private String name;
@ExcelProperty(value = "年齡",index = 1)
private String age;
@ExcelProperty(value = "郵箱",index = 2)
private String email;
@ExcelProperty(value = "地址",index = 3)
private String address;
@ExcelProperty(value = "性別",index = 4)
private String sax;
@ExcelProperty(value = "高度",index = 5)
private String heigh;
@ExcelProperty(value = "備註",index = 6)
private String last;
}
@ExcelProperty(value = 「姓名」,index = 0) value是表頭數據,默認會寫在excel的表頭位置,index表明第幾列。
@Test
public void test1() throws FileNotFoundException {
OutputStream out = new FileOutputStream("/Users/jipengfei/78.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
//寫第一個sheet, sheet1 數據全是List<String> 無模型映射關係
Sheet sheet1 = new Sheet(1, 0,ExcelPropertyIndexModel.class);
writer.write(getData(), sheet1);
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
每行數據是一個java模型有表頭—-表頭層級爲多層級
生成Excel格式以下圖:
java模型寫法以下:
public class MultiLineHeadExcelModel extends BaseRowModel {
@ExcelProperty(value = {"表頭1","表頭1","表頭31"},index = 0)
private String p1;
@ExcelProperty(value = {"表頭1","表頭1","表頭32"},index = 1)
private String p2;
@ExcelProperty(value = {"表頭3","表頭3","表頭3"},index = 2)
private int p3;
@ExcelProperty(value = {"表頭4","表頭4","表頭4"},index = 3)
private long p4;
@ExcelProperty(value = {"表頭5","表頭51","表頭52"},index = 4)
private String p5;
@ExcelProperty(value = {"表頭6","表頭61","表頭611"},index = 5)
private String p6;
@ExcelProperty(value = {"表頭6","表頭61","表頭612"},index = 6)
private String p7;
@ExcelProperty(value = {"表頭6","表頭62","表頭621"},index = 7)
private String p8;
@ExcelProperty(value = {"表頭6","表頭62","表頭622"},index = 8)
private String p9;
}
寫Excel寫法同上,只需將ExcelPropertyIndexModel.class改成MultiLineHeadExcelModel.class
一個Excel多個sheet寫法
@Test
public void test1() throws FileNotFoundException {
OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false);
//寫第一個sheet, sheet1 數據全是List<String> 無模型映射關係
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("第一個sheet");
writer.write(getListString(), sheet1);
//寫第二個sheet sheet2 模型上打有表頭的註解,合併單元格
Sheet sheet2 = new Sheet(2, 3, MultiLineHeadExcelModel.class, "第二個sheet", null);
sheet2.setTableStyle(getTableStyle1());
writer.write(getModeldatas(), sheet2);
//寫sheet3 模型上沒有註解,表頭數據動態傳入
List<List<String>> head = new ArrayList<List<String>>();
List<String> headCoulumn1 = new ArrayList<String>();
List<String> headCoulumn2 = new ArrayList<String>();
List<String> headCoulumn3 = new ArrayList<String>();
headCoulumn1.add("第一列");
headCoulumn2.add("第二列");
headCoulumn3.add("第三列");
head.add(headCoulumn1);
head.add(headCoulumn2);
head.add(headCoulumn3);
Sheet sheet3 = new Sheet(3, 1, NoAnnModel.class, "第三個sheet", head);
writer.write(getNoAnnModels(), sheet3);
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
一個sheet中有多個表格
@Test
public void test2() throws FileNotFoundException {
OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false);
//寫sheet1 數據全是List<String> 無模型映射關係
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("第一個sheet");
Table table1 = new Table(1);
writer.write(getListString(), sheet1, table1);
writer.write(getListString(), sheet1, table1);
//寫sheet2 模型上打有表頭的註解
Table table2 = new Table(2);
table2.setTableStyle(getTableStyle1());
table2.setClazz(MultiLineHeadExcelModel.class);
writer.write(getModeldatas(), sheet1, table2);
//寫sheet3 模型上沒有註解,表頭數據動態傳入,此狀況下模型field順序與excel現實順序一致
List<List<String>> head = new ArrayList<List<String>>();
List<String> headCoulumn1 = new ArrayList<String>();
List<String> headCoulumn2 = new ArrayList<String>();
List<String> headCoulumn3 = new ArrayList<String>();
headCoulumn1.add("第一列");
headCoulumn2.add("第二列");
headCoulumn3.add("第三列");
head.add(headCoulumn1);
head.add(headCoulumn2);
head.add(headCoulumn3);
Table table3 = new Table(3);
table3.setHead(head);
table3.setClazz(NoAnnModel.class);
table3.setTableStyle(getTableStyle2());
writer.write(getNoAnnModels(), sheet1, table3);
writer.write(getNoAnnModels(), sheet1, table3);
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4. 測試數據分析
從上面的性能測試能夠看出easyexcel在解析耗時上比poiuserModel模式弱了一些。主要緣由是我內部採用了反射作模型字段映射,中間我也加了cache,但感受這點差距能夠接受的。但在內存消耗上差異就比較明顯了,easyexcel在後面文件再增大,內存消耗幾乎不會增長了。湯姆叔叔的小屋讀書筆記(http://www.simayi.net/dushubiji/6445.html)摘抄好詞好句及感悟賞析,但poi userModel就不同了,簡直就要爆掉了。想一想一個excel解析200M,同時有20我的再用估計一臺機器就掛了。
5. 百萬數據解析對比
easyexcel解析百萬數據內存圖以下:
easyexcel解析百萬數據內存圖
poi解析百萬數據內存圖以下:
poi解析百萬數據內存圖
從上面兩圖能夠看出,easyexcel解析時內存消耗不多,最多消耗不到50M;POI解析過程當中直接飄升到1.5G左右,系統內存耗盡,程序掛掉。java