《開源精選》是我們分享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




 
			 
			 
			 
			 
			 
			 
			 
			 
			 
			 
			 
			 
			 
			 
			 
			 
			