《開源精選》是我們分享Github、Gitee等開源社群中優質專案的欄目,包括技術、學習、實用與各種有趣的內容。本期推薦的是一個阿里開源基於Java的Excel解析工具——EasyExcel。
Java解析、比較有名的框架有Apache poi、jxl,但他們都存在一個嚴重的問題就是消耗記憶體,poi有專門的SAX模式可以一定程度地解決一些記憶體問題,但poi還是有一些缺陷,比如部分版本Excel解壓縮以及解壓後儲存都是在記憶體中完成的,記憶體還是有很多消耗。easyexcel重寫了poi對Excel的解析,一個3M的Excel檔案使用poi解析仍然需要100M左右記憶體,改用easyexcel後可以降低到幾M,再大的excel也不會出現記憶體呼叫。
最新版本
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
示例
- 讀Excel
物件
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
private String string;
private Date date;
private Double doubleData;
}
監聽器
// 有個很重要的點 DemoDataListener 不能被spring管理,要每次讀取excel都要new,然後裡面用到spring可以構造方法傳進去
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {
/**
* 每隔5條儲存資料庫,實際使用中可以100條,然後清理list ,方便記憶體回收
*/
private static final int BATCH_COUNT = 100;
/**
* 快取的資料
*/
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 假設這個是一個DAO,當然有業務邏輯這個也可以是一個service。當然如果不用儲存這個物件沒用。
*/
private DemoDAO demoDAO;
public DemoDataListener() {
// 這裡是demo,所以隨便new一個。實際使用如果到了spring,請使用下面的有參建構函式
demoDAO = new DemoDAO();
}
/**
* 如果使用了spring,請使用這個構造方法。每次建立Listener的時候需要把spring管理的類傳進來
*
* @param demoDAO
*/
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
/**
* 這個每一條資料解析都會來呼叫
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
log.info("解析到一條資料:{}", JSON.toJSONString(data));
cachedDataList.add(data);
// 達到BATCH_COUNT了,需要去儲存一次資料庫,防止資料幾萬條資料在記憶體,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 儲存完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有資料解析完成了 都會來呼叫
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 這裡也要儲存資料,確保最後遺留的資料也儲存到資料庫
saveData();
log.info("所有資料解析完成!");
}
/**
* 加上儲存資料庫
*/
private void saveData() {
log.info("{}條資料,開始儲存資料庫!", cachedDataList.size());
demoDAO.save(cachedDataList);
log.info("儲存資料庫成功!");
}
}
持久層
/**
* 假設這個是你的DAO儲存。當然還要這個類讓spring管理,當然你不用需要儲存,也不需要這個類。
**/
public class DemoDAO {
public void save(List<DemoData> list) {
// 如果是mybatis,儘量別直接呼叫多次insert,自己寫一個mapper裡面新增一個方法batchInsert,所有資料一次性插入
}
}
最簡單的讀示例程式碼
/**
* 最簡單的讀
* <p>
* 1. 建立excel對應的實體物件 參照{@link DemoData}
* <p>
* 2. 由於預設一行行的讀取excel,所以需要建立excel一行一行的回撥監聽器,參照{@link DemoDataListener}
* <p>
* 3. 直接讀即可
*/
@Test
public void simpleRead() {
// 寫法1:JDK8+ ,不用額外寫一個DemoDataListener
// since: 3.0.0-beta1
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 檔案流會自動關閉
// 這裡每次會讀取3000條資料 然後返回過來 直接呼叫使用資料就行
EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
for (DemoData demoData : dataList) {
log.info("讀取到一條資料{}", JSON.toJSONString(demoData));
}
})).sheet().doRead();
// 寫法2:
// 匿名內部類 不用額外寫一個DemoDataListener
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 檔案流會自動關閉
EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() {
/**
* 單次快取的資料量
*/
public static final int BATCH_COUNT = 100;
/**
*臨時儲存
*/
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
@Override
public void invoke(DemoData data, AnalysisContext context) {
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 儲存完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
}
/**
* 加上儲存資料庫
*/
private void saveData() {
log.info("{}條資料,開始儲存資料庫!", cachedDataList.size());
log.info("儲存資料庫成功!");
}
}).sheet().doRead();
// 有個很重要的點 DemoDataListener 不能被spring管理,要每次讀取excel都要new,然後裡面用到spring可以構造方法傳進去
// 寫法3:
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 檔案流會自動關閉
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
// 寫法4:
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 一個檔案一個reader
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
// 構建一個sheet 這裡可以指定名字或者no
ReadSheet readSheet = EasyExcel.readSheet(0).build();
// 讀取一個sheet
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 這裡千萬別忘記關閉,讀的時候會建立臨時檔案,到時磁碟會崩的
excelReader.finish();
}
}
}
64M記憶體20秒讀取75M(46W行25列)的Excel:
當然還有急速模式能更快,但是記憶體佔用會在100M多一點。
更多內容大家可自行前往閱讀。
開源地址:https://github.com/alibaba/easyexcel