茫茫網海中的冷日
         
茫茫網海中的冷日
發生過的事,不可能遺忘,只是想不起來而已!
 恭喜您是本站第 1729595 位訪客!  登入  | 註冊
主選單

Google 自訂搜尋

Goole 廣告

隨機相片
HoneyMoon_Day2_00105.jpg

授權條款

使用者登入
使用者名稱:

密碼:


忘了密碼?

現在就註冊!

爪哇咖啡屋 : [轉貼]JAVA 使用 easyexcel 操作 Excel

發表者 討論內容
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15771
[轉貼]Java解析excel工具easyexcel 助你快速簡單避免OOM

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模式。在上層做了模型轉換的封裝,讓使用者更加簡單方便

easyexcel核心功能

  • 讀任意大小的03、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解析03、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) {
//1、入庫調用接口
}
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在後面文件再增大,內存消耗幾乎不會增加了。但poi userModel就不一樣了,簡直就要爆掉了。想想一個excel解析200M,同時有20個人再用估計一台機器就掛了。

5. 百萬數據解析對比

easyexcel解析百萬數據內存圖如下:

poi解析百萬數據內存圖如下:

從上面兩圖可以看出,easyexcel解析時內存消耗很少,最多消耗不到50M;POI解析過程中直接飄升到1.5G左右,系統內存耗盡,程序掛掉。

GitHub地址: https://github.com/alibaba/easyexcel



原文出處: Java解析excel工具easyexcel 助你快速简单避免OOM - 红豆和绿豆的博客 - CSDN博客
前一個主題 | 下一個主題 | | | |

討論串




Powered by XOOPS 2.0 © 2001-2008 The XOOPS Project|