EasyExcel實現Excel文件導入

EasyExcel簡介

EasyExcel是一個基於Java的簡單、省內存的讀寫Excel的開源項目。在儘可能節約內存的情況下支持讀寫百M的Excel。

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

官方文檔: https://www.yuque.com/easyexcel/doc/easyexcel

Excel解析流程圖:

 

 EasyExcel讀取Excel的解析原理:

2 EasyExcel使用

2.1 EasyExcel相關依賴

添加maven依賴, 依賴的poi最低版本3.17

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.3</version>
</dependency>

2.2 讀Excel

2.2.1 最簡單的讀(方式一)

Excel數據類型

字符串標題日期標題數字標題
小明 2020-05-05 10:10:10 888.88

數據模板

注意: Java類中的屬性字段順序和Excel中的表頭字段順序一致, 可以不寫@ExcelProperty

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class DemoData {
    // 根據Excel中指定列名或列的索引讀取
    @ExcelProperty(value = "字符串標題", index = 0)
    private String name;
    @ExcelProperty(value = "日期標題", index = 1)
    private Date hireDate;
    @ExcelProperty(value = "數字標題", index = 2)
    private Double salary;
    // lombok 會生成getter/setter方法
}

讀取excel代碼

關鍵是寫一個監聽器,實現AnalysisEventListener, 每解析一行數據會調用invoke方法返回解析的數據, 當全部解析完成後會調用doAfterAllAnalysed方法. 我們重寫invoke方法和doAfterAllAnalysed方法即可.

@Test
public void testReadExcel() {
    // 讀取的excel文件路徑
    String filename = "D:\\study\\excel\\read.xlsx";
    // 讀取excel
    EasyExcel.read(filename, DemoData.class, new AnalysisEventListener<DemoData>() {
        // 每解析一行數據,該方法會被調用一次
        @Override
        public void invoke(DemoData demoData, AnalysisContext analysisContext) {
            System.out.println("解析數據爲:" + demoData.toString());
        }
        // 全部解析完成被調用
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            System.out.println("解析完成...");
            // 可以將解析的數據保存到數據庫
        }
    }).sheet().doRead();
}

效果:

2.2.2 最簡單的讀(方式二)

讀excel的方式二代碼

@Test
public void testReadExcel2() {
    // 讀取的excel文件路徑
    String filename = "D:\\study\\excel\\read.xlsx";
    // 創建一個數據格式來裝讀取到的數據
    Class<DemoData> head = DemoData.class;
    // 創建ExcelReader對象
    ExcelReader excelReader = EasyExcel.read(filename, head, new AnalysisEventListener<DemoData>() {
        // 每解析一行數據,該方法會被調用一次
        @Override
        public void invoke(DemoData demoData, AnalysisContext analysisContext) {
            System.out.println("解析數據爲:" + demoData.toString());
        }
        // 全部解析完成被調用
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            System.out.println("解析完成...");
            // 可以將解析的數據保存到數據庫
        }
    }).build();
    // 創建sheet對象,並讀取Excel的第一個sheet(下標從0開始), 也可以根據sheet名稱獲取
    ReadSheet sheet = EasyExcel.readSheet(0).build();
    // 讀取sheet表格數據, 參數是可變參數,可以讀取多個sheet
    excelReader.read(sheet);
    // 需要自己關閉流操作,在讀取文件時會創建臨時文件,如果不關閉,會損耗磁盤,嚴重的磁盤爆掉
    excelReader.finish();
}

2.2.3 格式化Excel中的數據格式

要讀取的源數據, 日期格式是yyyy年MM月dd日 HH時mm分ss秒, 數字帶小數點

 

 

 數據模板

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class DemoData {
    @ExcelProperty(value = "字符串標題", index = 0)
    private String name;
    @ExcelProperty(value = "日期標題", index = 1)
   // 格式化日期類型數據
    @DateTimeFormat(value = "yyyy年MM月dd日 HH時mm分ss秒")
    private Date hireDate;
    @ExcelProperty(value = "數字標題", index = 2)
   // 格式化數字類型數據,保留一位小數
    @NumberFormat(value = "###.#")
    private String salary;
    //注意: @NumberFormat對於Double類型的數據格式化會失效,建議使用String類型接收數據進行格式化
//    private Double salary;
    // lombok 會生成getter/setter方法
}

讀取excel代碼同上面讀取方式一樣.

2.2.4 讀取多個sheet表格

2.2.4.1 讀所有sheet

讀方式一, 使用ExcelReaderBuilder#doReadAll方法

@Test
    public void testReadExcel() {
        // 讀取的excel文件路徑
        String filename = "D:\\study\\excel\\read.xlsx";
        // 讀取excel
        EasyExcel.read(filename, DemoData.class, new AnalysisEventListener<DemoData>() {

            // 每解析一行數據,該方法會被調用一次
            @Override
            public void invoke(DemoData demoData, AnalysisContext analysisContext) {
                System.out.println("解析數據爲:" + demoData.toString());
            }

            // 全部解析完成被調用
            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                System.out.println("解析完成...");
                // 可以將解析的數據保存到數據庫
            }
        })
//         .sheet(0).doRead();
           .doReadAll(); // 讀取全部sheet

    }

讀方式二, 使用ExcelReader#readAll方法

@Test
    public void testReadExcel2() {
        // 讀取的excel文件路徑
        String filename = "D:\\study\\excel\\read.xlsx";
        // 創建一個數據格式來裝讀取到的數據
        Class<DemoData> head = DemoData.class;
        // 創建ExcelReader對象
        ExcelReader excelReader = EasyExcel.read(filename, head, 
                                    new AnalysisEventListener<DemoData>() {
            // 每解析一行數據,該方法會被調用一次
            @Override
            public void invoke(DemoData demoData, AnalysisContext analysisContext) {
                System.out.println("解析數據爲:" + demoData.toString());
            }

            // 全部解析完成被調用
            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                System.out.println("解析完成...");
                // 可以將解析的數據保存到數據庫
            }
        }).build();
        // 創建sheet對象,並讀取Excel的第一個sheet(下標從0開始), 也可以根據sheet名稱獲取
        ReadSheet sheet = EasyExcel.readSheet(0).build();
        // 讀取sheet表格數據 , 參數是可變參數,可以讀取多個sheet
//        excelReader.read(sheet);
        excelReader.readAll(); // 讀所有sheet
        // 需要自己關閉流操作,在讀取文件時會創建臨時文件,如果不關閉,會損耗磁盤,嚴重的磁盤爆掉
        excelReader.finish();
    }

2.2.4.2 讀指定的多個sheet

不同sheet表格的數據模板可能不一樣,這時候就需要分別構建不同的sheet對象,分別爲其指定對應的數據模板.

@Test-
public void testReadExcel3() {
    // 讀取的excel文件路徑
    String filename = "D:\\study\\excel\\read.xlsx";
    // 構建ExcelReader對象
    ExcelReader excelReader = EasyExcel.read(filename).build();
    // 構建sheet對象
    ReadSheet sheet0 = EasyExcel.readSheet(0)
            .head(DemoData.class) // 指定sheet0的數據模板
            .registerReadListener(new AnalysisEventListener<DemoData>() {
                // 每解析一行數據,該方法會被調用一次
              @Override
              public void invoke(DemoData demoData, AnalysisContext analysisContext) {
                    System.out.println("解析數據爲:" + demoData.toString());
              }

                // 全部解析完成被調用
                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                    System.out.println("解析完成...");
                    // 可以將解析的數據保存到數據庫
                }
            }).build();

    //取sheet,有幾個就構建幾個sheet進行讀取
    excelReader.read(sheet0);
    // 需要自己關閉流操作,在讀取文件時會創建臨時文件,如果不關閉,會損耗磁盤,嚴重的磁盤爆掉
    excelReader.finish();
} 

3 EasyExcel使用優化

3.1 監聽器優化

上面章節的讀取Excel的程序弊端:

每次解析不同數據模型都要新增一個監聽器, 重複工作量大;

即使用了匿名內部類,程序也顯得臃腫;

數據處理一般都會存在於項目的service中, 監聽器難免會依賴dao層, 導致程序耦合度高.

解決方案:

通過泛型指定數據模型類型, 針對不同類型的數據模型只需要定義一個監聽器即可;

使用jdk8新特性中的函數式接口, 將數據處理從監聽器中剝離出去, 進行解耦.
監聽器代碼:

/**
 * 類描述:easyexcel工具類
 * @Author 
 * @Date 
 * @Version 1.0
 */
public class EasyExcelUtils<T> {

    /**
     * 獲取讀取Excel的監聽器對象
     * 爲了解耦及減少每個數據模型bean都要創建一個監聽器的臃腫, 使用泛型指定數據模型類型
     * 使用jdk8新特性中的函數式接口 Consumer
     * 可以實現任何數據模型bean的數據解析, 不用重複定義監聽器
     * @param consumer 處理解析數據的函數, 一般可以是數據入庫邏輯的函數
     * @param threshold 閾值,達到閾值就處理一次存儲的數據
     * @param <T> 數據模型泛型
     * @return 返回監聽器
     */
    public static <T> AnalysisEventListener<T> getReadListener(Consumer<List<T>> consumer, int threshold) {

        return new AnalysisEventListener<T>() {

            /**
             * 存儲解析的數據 T t
             */
           // ArrayList基於數組實現, 查詢更快
//            List<T> dataList = new ArrayList<>(threshold);
           // LinkedList基於雙向鏈表實現, 插入和刪除更快
            List<T> dataList = new LinkedList<>(); 

            /**
             * 每解析一行數據事件調度中心都會通知到這個方法, 訂閱者1
             * @param data 解析的每行數據
             * @param context
             */
            @Override
            public void invoke(T data, AnalysisContext context) {
                dataList.add(data);
                // 達到閾值就處理一次存儲的數據
                if (dataList.size() >= threshold) {
                    consumer.accept(dataList);
                    dataList.clear();
                }
            }

            /**
             * excel文件解析完成後,事件調度中心會通知到該方法, 訂閱者2
             * @param context
             */
            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                // 最後閾值外的數據做處理
                if (dataList.size() > 0) {
                    consumer.accept(dataList);
                }
            }
        };

    }

    /**
     * 獲取讀取Excel的監聽器對象, 不指定閾值, 默認閾值爲 2000
     * @param consumer
     * @param <T>
     * @return
     */
    public static <T> AnalysisEventListener<T> getReadListener(Consumer<List<T>> 
                                                               consumer) {
        return getReadListener(consumer, 2000);
    }
}

再來看讀取Excel的 代碼:

/**
 * 採用解耦的自定義監聽器讀取Excel, 可以實現任何數據模型bean的讀取
 */
@Test
public void testReadExcelN() {
    // 讀取的excel文件路徑
    String filename = "D:\\study\\excel\\user1.xlsx";
    // 讀取excel
    EasyExcel.read(filename, UserModel.class,     
                   EasyExcelUtils.getReadListener(dataProcess()))
            .doReadAll(); // 讀取全部sheet
}

/**
 *  傳給監聽器的是一個處理解析數據的函數, 當調用consumer的accept方法時就會調用傳遞的函數邏輯
 *  這裏傳遞的函數是對解析結果集的遍歷打印操作, 也可以是數據入庫操作
 * @return
 */
public Consumer<List<UserModel>> dataProcess() {
    Consumer<List<UserModel>> consumer = users -> users.forEach(System.out::println);
    return consumer;
}

 

 

參考網址:

https://blog.csdn.net/u013044713/article/details/120249233

https://github.com/alibaba/easyexcel

https://www.jianshu.com/p/10e05e89b8a0

http://events.jianshu.io/p/52d9e12e93bd

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章