Java 使用 POI 操做 Excel

Apache POI 基本介紹

Apache POI 是 Apache 軟件基金會提供的 100% 開源庫。支持 Excel 庫的全部基本功能。html

image.png | left | 629x536

圖片來源:易百教程java

基本概念

在 POI 中,Workbook表明着一個 Excel 文件(工做簿),Sheet表明着 Workbook 中的一個表格,Row 表明 Sheet 中的一行,而 Cell 表明着一個單元格。 HSSFWorkbook對應的就是一個 .xls 文件,兼容 Office97-2003 版本。 XSSFWorkbook對應的是一個 .xlsx 文件,兼容 Office2007 及以上版本。 在 HSSFWorkbook 中,Sheet接口 的實現類爲 HSSFSheet,Row接口 的實現類爲HSSFRow,Cell 接口的實現類爲 HSSFCell。 XSSFWorkbook 中實現類的命名方式相似,在 Sheet、Row、Cell 前加 XSSF 前綴便可。git

引入依賴

<!-- 基本依賴,僅操做 xls 格式只需引入此依賴 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.14</version>
</dependency>
<!-- 使用 xlsx 格式須要額外引入此依賴 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.14</version>
</dependency>
複製代碼

使用 POI

使用 POI 的目的就是爲了在 Java 中解析/操做 Excel 表格,實現 Excel 的導入/導出的功能,接下來咱們依次來看它們的實現代碼及注意事項。程序員

導出

導出操做即便用 Java 寫出數據到 Excel 中,常見場景是將頁面上的數據(多是通過條件查詢的)導出,這些數據多是財務數據,也多是商品數據,生成 Excel 後返回給用戶下載文件。 該操做主要涉及 Excel 的建立及使用流輸出的操做,在 Excel 建立過程當中,可能還涉及到單元格樣式的操做。github

建立並導出基本數據

進行導出操做的第一步是建立 Excel 文件,咱們寫一個方法,參數是須要寫入 Excel 表格的數據和生成 Excel 方式(HSSF,XSSF),返回一個 Workbook 接口對象。 在方法內部咱們採用反射來建立 Workbook 的實例對象。數據庫

代碼

探索階段,咱們先將數據類型限定爲 List,並把列數限定爲某個數字,生成一個表格。 代碼以下:apache

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import java.util.List;
/** * Excel 工廠類,負責 Workbook 的生成和解析 * * @author calmer * @since 2018/12/5 11:19 */
public class ExcelFactory {
    /** * 構造 Workbook 對象,具體實例化哪一種對象由 type 參數指定 * @param data 要導出的數據 * @param type Excel 生成方式 * @return 對應 type 的工做簿實例對象 * @throws Exception 反射生成對象時出現的異常 * <li>InstantiationException</li> * <li>IllegalAccessException</li> * <li>InstantiationException</li> */
    public static Workbook createExcel(List data,String type) throws Exception{
        //根據 type 參數生成工做簿實例對象
        Workbook workbook = (Workbook) Class.forName(type).newInstance();
        //這裏還能夠指定 sheet 的名字
        //Sheet sheet = workbook.createSheet("sheetName");
        Sheet sheet = workbook.createSheet();
        // 限定列數
        int cols = 10;
        int rows = data.size() / cols;
        int index = 0;
        for (int rowNum = 0; rowNum < rows; rowNum++) {
            Row row = sheet.createRow(rowNum);
            for (int colNum = 0; colNum < cols; colNum++) {
                Cell cell = row.createCell(colNum);
                cell.setCellValue(data.get(index++).toString());
            }
        }
        return workbook;
    }
}
複製代碼

調用時,咱們生成好數據並構造好 Workbook 對象,再調用 Workbook 的 write(OutputStream stream) 方法生成 Excel 文件。數組

List<String> strings = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    strings.add(Integer.toString(i+1));
}
FileOutputStream out = new FileOutputStream("F:\\testXSSF.xlsx");
ExcelFactory.createExcel(strings,"org.apache.poi.xssf.usermodel.XSSFWorkbook").write(out);
out.close();
複製代碼

生成結果:數據結構

image.png | left | 747x402

image.png | left | 747x223

問題

以上代碼已經完成簡單的 Excel 文件生成操做,但其中還有幾點問題沒有解決框架

  • 實際場景下,Excel 表格中可能並不會存 Integer、String 這種基本數據結構的數據,更多的多是對象數據(JSON、List),須要有表頭,並將對象對應的屬性一行行的顯示出來(參考數據庫查詢語句執行的結果)。而且表頭的樣式必定是要控制的。
  • 咱們並無對方法中 type 屬性進行限制,即外部能夠傳來任何相似「a」、「b」這樣的無效值,屆時程序會拋出異常,可使用靜態常量或枚舉類來限定,這樣能夠加強代碼可讀性和健壯性。這裏我並不想用靜態常量或枚舉類,打算使用註解的方式來控制參數的有效性。
  • 完善

    咱們已經明確了兩個問題:

    1. 以前的程序並不能在實際場景使用,咱們須要將其完善到具備處理實際數據的能力。
    2. 利用註解限定參數的有效性。

    咱們先來解決第二個問題,即參數的問題。

    使用註解限定參數

    首先建立一個註解類

    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /** * * @author calmer * @since 2018/12/5 12:27 */
    @Retention(RetentionPolicy.SOURCE)
    public @interface ExcelType {
        String HSSF = "org.apache.poi.hssf.usermodel.HSSFWorkbook";
        String XSSF = "org.apache.poi.xssf.usermodel.XSSFWorkbook";
    }
    
    複製代碼

    在方法參數上加上註解

    public static Workbook createExcel(List data, @ExcelType String type) throws Exception {
        //內容省略
    
    }
    複製代碼

    調用時

    ExcelFactory.createExcel(list,ExcelType.HSSF).write(out);
    複製代碼

    關於使用註解來限定參數的取值範圍這種方式,我也是偶然看到過,但是這種方式在我這裏編譯器並不會給任何提示,我對註解瞭解不夠,之後有機會要再好好研究一下。

    解決實際數據問題

    在實際應用中,很常見的狀況是咱們有不少實體類,好比 Person,Product,Order 等,藉助反射,咱們能夠獲取任意實體類的屬性列表、getter 方法,因此目前,我打算利用反射,來處理多個對象的 Excel 導出。 首先咱們建立一個方法,用來獲取某個對象的屬性列表(暫時不考慮要獲取父類屬性的狀況)。

    /** * 獲取對象的屬性名數組 * @param clazz Class 對象,用於獲取該類的信息 * @return 該類的全部屬性名數組 */
    private static String[] getFieldsName(Class clazz){
        Field[] fields = clazz.getDeclaredFields();
        String[] fieldNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
            fieldNames[i] = fields[i].getName();
        }
        return fieldNames;
    }
    複製代碼

    而後咱們完善 createExcel() 方法

    public static Workbook createExcel(List data, @ExcelType String type) throws Exception {
        if(data == null || data.size() == 0){
            throw new Exception("數據不能爲空");
        }
        //根據類型生成工做簿
        Workbook workbook = (Workbook) Class.forName(type).newInstance();
        //新建表格
        Sheet sheet = workbook.createSheet();
        //生成表頭
        Row thead = sheet.createRow(0);
        String[] fieldsName = getFieldsName(data.get(0).getClass());
        for (int i = 0; i < fieldsName.length; i++) {
            Cell cell = thead.createCell(i);
            cell.setCellValue(fieldsName[i]);
        }
        //保存全部屬性的getter方法名
        Method[] methods = new Method[fieldsName.length];
        for (int i = 0; i < data.size(); i++) {
            Row row = sheet.createRow(i+1);
            Object obj = data.get(i);
            for (int j = 0; j < fieldsName.length; j++) {
                //加載第一行數據時,初始化全部屬性的getter方法
                if(i == 0){
                    String fieldName = fieldsName[j];
                    //處理布爾值命名 "isXxx" -> "setXxx"
                    if (fieldName.contains("is")) {
                        fieldName = fieldName.split("is")[1];
                    }
                    methods[j] = obj.getClass().getMethod("get" +
                            fieldName.substring(0,1).toUpperCase() +
                            fieldName.substring(1));
                }
                Cell cell = row.createCell(j);
                Object value = methods[j].invoke(obj);
                //注意判斷 value 值是否爲空
                if(value == null){
                    value = "無";
                }
                cell.setCellValue(value.toString());
            }
        }
        return workbook;
    }
    複製代碼

    測試

    以上代碼基本知足一開始的需求,即以類的屬性名爲表頭並生成表格。接下來咱們生成必定量的數據,並測試導出效果。 實體類代碼

    /** * * @author calmer * @since 2018/12/5 14:50 */
    public class Person {
        private Integer id;
        private String name;
        private Integer age;
        private String hobby;
        private String job;
        private String address;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getHobby() {
            return hobby;
        }
    
        public void setHobby(String hobby) {
            this.hobby = hobby;
        }
    
        public String getJob() {
            return job;
        }
    
        public void setJob(String job) {
            this.job = job;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    }
    複製代碼

    測試類代碼

    List<Person> list = new ArrayList<>();
    for (int i = 0; i < 60000; i++) {
        int num = i + 1;
        Person person = new Person();
        person.setId(num);
        person.setName("張三-"+(num));
        person.setAddress("花園路"+num+"號"+(int)Math.ceil(Math.random()*10)+"號樓");
        person.setAge(i+18);
        person.setHobby("洗臉刷牙打DOTA");
        person.setJob("程序員");
        list.add(person);
    }
    FileOutputStream out = new FileOutputStream("F:\\testXSSF.xlsx");
    ExcelFactory.createExcel(list,ExcelType.XSSF).write(out);
    out.close();
    複製代碼

    生成的結果以下

    image.png | left | 747x402

    image.png | left | 747x402

    其餘

    這裏測試的時候我使用6W的數據,因此程序進行的比較慢,用時以下:

    image.png | left | 387x113

    像這種大數據量的導出,咱們可使用 SXSSF 的方式,網上也有不少例子,官網的對比。使用 SXSSF 方式導出用時以下:

    image.png | left | 405x114

    能夠看到時間縮短了不少。接下來咱們單獨來了解一下如何控制表格的樣式。

    樣式

    一般,咱們須要控制的樣式有兩個部分,一個是表頭部分的樣式,另外一個是普通單元格的樣式。此次咱們就僅建立兩個方法演示樣式的設置方式。 在 POI 中,控制單元格樣式的對象是 CellStyle 接口,能夠經過 Workbook 的createStyle 方法得到實例對象,這裏咱們寫一個方法設置表頭的樣式。

    private static CellStyle getTheadStyle(Workbook workbook){
        CellStyle style = workbook.createCellStyle();
        //設置填充色
        style.setFillForegroundColor(IndexedColors.LIGHT_BLUE.index);
        style.setFillPattern(CellStyle.SOLID_FOREGROUND);
        //設置對齊方式
        style.setAlignment(CellStyle.ALIGN_CENTER);
        //字體樣式
        Font font = workbook.createFont();
        //設置字體名稱
        font.setFontName("華文隸書");
        //斜體
        font.setItalic(true);
        //字體顏色
        font.setColor(IndexedColors.YELLOW.index);
        //字體大小
        font.setFontHeightInPoints((short)12);
        //不要忘記這句
        style.setFont(font);
        return style;
    }
    複製代碼

    調用

    Row thead = sheet.createRow(0);
    //設置行高
    thead.setHeight((short) 500);
    //僅使用 setRowStyle 方法會對除有值的表頭設置樣式
    thead.setRowStyle(style);
    String[] fieldsName = getFieldsName(data.get(0));
    for (int i = 0; i < fieldsName.length; i++) {
        Cell cell = thead.createCell(i);
        cell.setCellValue(fieldsName[i]);
        //在這裏循環爲每一個有值的表頭設置樣式。
        //結合上面的 setRowStyle 會將表頭行所有設置樣式
        cell.setCellStyle(style);
    }
    複製代碼

    接下來咱們寫獲取普通單元格樣式的方法

    private static CellStyle getCommonStyle(Workbook workbook){
        CellStyle style = workbook.createCellStyle();
        //設置填充色
        style.setFillForegroundColor(IndexedColors.GREEN.index);
        style.setFillPattern(CellStyle.SOLID_FOREGROUND);
        //設置居中對齊
        style.setAlignment(CellStyle.ALIGN_CENTER);
        Font font = workbook.createFont();
        font.setFontName("華文彩雲");
        //不要忘記這句
        style.setFont(font);
        return style;
    }
    複製代碼

    完整調用

    public static Workbook createExcel(List data, @ExcelType String type) throws Exception {
        if(data == null || data.size() == 0){
            throw new Exception("數據不能爲空");
        }
        //根據類型生成工做簿
        Workbook workbook = (Workbook) Class.forName(type).newInstance();
        //生成樣式
        CellStyle style = getTheadStyle(workbook);
        //新建表格
        Sheet sheet = workbook.createSheet();
        //生成表頭
        Row thead = sheet.createRow(0);
        //設置行高
        thead.setHeight((short) 500);
        //僅使用 setRowStyle 方法會對除有值的表頭設置樣式
        thead.setRowStyle(style);
        String[] fieldsName = getFieldsName(data.get(0));
        for (int i = 0; i < fieldsName.length; i++) {
            Cell cell = thead.createCell(i);
            cell.setCellValue(fieldsName[i]);
            //在這裏循環爲每一個有值的表頭設置樣式。
            //結合上面的 setRowStyle 會將表頭行所有設置樣式
            cell.setCellStyle(style);
        }
        //保存全部屬性的getter方法名
        Method[] methods = new Method[fieldsName.length];
        //獲取普通單元格樣式
        style = getCommonStyle(workbook);
        for (int i = 0; i < data.size(); i++) {
            Row row = sheet.createRow(i+1);
            Object obj = data.get(i);
            for (int j = 0; j < fieldsName.length; j++) {
                //加載第一行數據時,初始化全部屬性的getter方法
                if(i == 0){
                    String fieldName = fieldsName[j];
                    methods[j] = obj.getClass().getMethod("get" +
                            fieldName.substring(0,1).toUpperCase() +
                            fieldName.substring(1));
                }
                Cell cell = row.createCell(j);
                Object value = methods[j].invoke(obj);
                //注意判斷 value 值是否爲空
                if(value == null){
                    value = "無";
                }
                cell.setCellValue(value.toString());
                //設置單元格樣式
                cell.setCellStyle(style);
            }
        }
        return workbook;
    }
    複製代碼

    生成結果以下(忽視顏色搭配與美觀程度)

    image.png | left | 747x402

    注意

    這裏我運行的出了一個問題,在此記錄。 注意上面代碼的第 28 行和第 48 行,這裏咱們在 for 循環外面獲取 Style 對象,在 for 循環中循環設置單元格樣式的時候,始終使用的是__同一個__ Style。而最開始我測試的時候,並非這樣寫,而是像下面這樣:

    for (int i = 0; i < data.size(); i++) {
        Row row = sheet.createRow(i+1);
        Object obj = data.get(i);
        for (int j = 0; j < fieldsName.length; j++) {
            //加載第一行數據時,初始化全部屬性的getter方法
            if(i == 0){
                String fieldName = fieldsName[j];
                methods[j] = obj.getClass().getMethod("get" +
                        fieldName.substring(0,1).toUpperCase() +
                        fieldName.substring(1));
            }
            Cell cell = row.createCell(j);
            Object value = methods[j].invoke(obj);
            //注意判斷 value 值是否爲空
            if(value == null){
                value = "無";
            }
            cell.setCellValue(value.toString());
            //設置單元格樣式
            cell.setCellStyle(getCommonStyle(workbook));
        }
    }
    複製代碼

    注意 20 行,在 getCommonStyle 方法中,咱們每次調用都會使用 Workbook 對象建立一個 Style 對象,而咱們的數據一共有 6W 條,沒條數據又有 6 個屬性,咱們一共要渲染 36W 個單元格,也就是要生成 36W 個 Style 對象。因而,在我運行代碼時便出現了以下報錯。

    F:\java\jdk1.8.0_151\bin\java.exe 
    Exception in thread "main" java.lang.IllegalStateException: The maximum number of Cell Styles was exceeded. You can define up to 64000 style in a .xlsx Workbook
    	at org.apache.poi.xssf.model.StylesTable.createCellStyle(StylesTable.java:789)
    	at org.apache.poi.xssf.usermodel.XSSFWorkbook.createCellStyle(XSSFWorkbook.java:682)
    	at org.apache.poi.xssf.streaming.SXSSFWorkbook.createCellStyle(SXSSFWorkbook.java:869)
    	at com.xhc.study.util.poi.ExcelFactory.getCommonStyle(ExcelFactory.java:114)
    	at com.xhc.study.util.poi.ExcelFactory.createExcel(ExcelFactory.java:73)
    	at Test.main(Test.java:62)
    
    Process finished with exit code 1
    
    複製代碼

    這裏提示咱們最多讓一個 Workbook 對象生成 64000 個 Style 對象。 之後一些危險的操做仍是少作😉

    導入

    導入操做即便用 Java 讀取 Excel 中的數據,常見場景是在頁面上點擊導入按鈕,用戶選擇 Excel 文件,其中多是多條商品數據(包含編號、名稱、參數等信息),經過文件上傳功能將 Excel 讀取到咱們的程序中,解析其中的數據並存入數據庫中。

    讀取數據並打印

    導入操做主要依靠 Workbook 的一個構造函數,源碼以下

    /** * Constructs a XSSFWorkbook object, by buffering the whole stream into memory * and then opening an {@link OPCPackage} object for it. * * <p>Using an {@link InputStream} requires more memory than using a File, so * if a {@link File} is available then you should instead do something like * <pre><code> * OPCPackage pkg = OPCPackage.open(path); * XSSFWorkbook wb = new XSSFWorkbook(pkg); * // work with the wb object * ...... * pkg.close(); // gracefully closes the underlying zip file * </code></pre> */
    public XSSFWorkbook(InputStream is) throws IOException {
        super(PackageHelper.open(is));
    
        beforeDocumentRead();
        
        // Build a tree of POIXMLDocumentParts, this workbook being the root
        load(XSSFFactory.getInstance());
    
        // some broken Workbooks miss this...
        if(!workbook.isSetBookViews()) {
            CTBookViews bvs = workbook.addNewBookViews();
            CTBookView bv = bvs.addNewWorkbookView();
            bv.setActiveTab(0);
        }
    }
    複製代碼

    從這個構造函數來看,咱們只需提供一個輸入流,便能構造一個 Workbook 對象出來,接下來咱們首先寫一個處理 Workbook 的方法,參數爲一個 Workbook 對象,咱們在方法內部遍歷表格並輸出數據,這裏咱們默認該文件是一個規則的表格,即符合咱們以前生成的 Excel 那樣的格式。代碼以下

    /** * 讀取 Excel 數據並處理 * @param workbook 完整的 Workbook 對象 */
    public static void readExcel(Workbook workbook) {
        Sheet sheet = workbook.getSheetAt(0);
        //獲取總行數
        int rows = sheet.getPhysicalNumberOfRows();
        //去除表頭,從第 1 行開始打印
        for (int i = 0; i < rows; i++) {
            Row row = sheet.getRow(i);
            //獲取總列數
            int cols = row.getPhysicalNumberOfCells();
            for (int j = 0; j < cols; j++) {
                System.out.print(row.getCell(j) + "\t");
            }
            System.out.println();
        }
    }
    複製代碼

    爲了輸出方便,我已將 Excel 中的數據降爲 100 條。調用代碼以下

    FileInputStream in = new FileInputStream("F:\\testXSSF.xlsx");
    XSSFWorkbook workbook = new XSSFWorkbook(in);
    ExcelFactory.readExcel(workbook);
    in.close();
    複製代碼

    輸出結果以下

    image.png | left | 638x223

    image.png | left | 581x210

    數據已經拿到,接下來的問題是解析爲對象,畢竟咱們平時向數據庫保存數據使用的 ORM 框架通常都使用了傳輸對象。這裏咱們再次利用反射,完善代碼,使 readExcel 方法有讀取 Excel 中的數據並將其映射爲對象的能力。

    完善

    這裏須要明確幾個問題

    1. 如何肯定對象?
    2. 如何將未知對象的每一個字段的數據類型與 Excel 表格中的字符串數據進行轉換?
    3. 出現空值咱們如何解決?
    4. 日期格式的數據咱們如何轉換?有幾種日期格式?

    接下來咱們開始完善 readExcel 方法,代碼以下

    /** * 讀取 Excel 數據並處理 * * @param workbook 完整的 Workbook 對象 * @param clazz Excel 中存儲的數據的類的 Class 對象 * @param <T> 泛型 * @return 解析以後的對象列表,與泛型一致 * @throws Exception */
    public static <T> List<T> readExcel(Workbook workbook, Class<T> clazz) throws Exception {
        List<T> list = new ArrayList<>();
        Sheet sheet = workbook.getSheetAt(0);
        //獲取總行數
        int rows = sheet.getPhysicalNumberOfRows();
        //獲取全部字段名
        String[] fieldsName = getFieldsName(clazz);
        Method[] methods = new Method[fieldsName.length];
        //去除表頭,從第 1 行開始打印
        for (int i = 1; i < rows; i++) {
            T obj = clazz.newInstance();
            Row row = sheet.getRow(i);
            //獲取總列數
            int cols = row.getPhysicalNumberOfCells();
            //獲取全部屬性
            Field[] fields = clazz.getDeclaredFields();
            //處理對象的每個屬性
            for (int j = 0; j < cols; j++) {
                //第一次循環時初始化全部 setter 方法名
                if (i == 1) {
                    String fieldName = fieldsName[j];
                    //處理布爾值命名 "isXxx" -> "setXxx"
                    if (fieldName.contains("is")) {
                        fieldName = fieldName.split("is")[1];
                    }
                    methods[j] = obj.getClass().getMethod("set" +
                            fieldName.substring(0, 1).toUpperCase() +
                            fieldName.substring(1), fields[j].getType());
                }
                //先將單元格中的值按 String 保存
                String param = row.getCell(j).getStringCellValue();
                //屬性的類型
                String typeName = fields[j].getType().getSimpleName();
                //set 方法
                Method method = methods[j];
                //排除空值
                if (param == null || "".equals(param)) {
                    continue;
                }
                //根據對象的不一樣屬性字段轉換單元格中的數據類型並調用 set 方法賦值
                if ("Integer".equals(typeName) || "int".equals(typeName)) {
                    method.invoke(obj, Integer.parseInt(param));
                } else if ("Date".equals(typeName)) {
                    String pattern;
                    if (param.contains("CST")) {
                        //java.util.Date 的默認格式
                        pattern = "EEE MMM dd HH:mm:ss zzz yyyy";
                    } else if (param.contains(":")) {
                        //帶有時分秒的格式
                        pattern = "yyyy-MM-dd HH:mm:ss";
                    } else {
                        //簡單格式
                        pattern = "yyyy-MM-dd";
                    }
                    method.invoke(obj, new SimpleDateFormat(pattern, Locale.UK).parse(param));
                } else if ("Long".equalsIgnoreCase(typeName)) {
                    method.invoke(obj, Long.parseLong(param));
                } else if ("Double".equalsIgnoreCase(typeName)) {
                    method.invoke(obj, Double.parseDouble(param));
                } else if ("Boolean".equalsIgnoreCase(typeName)) {
                    method.invoke(obj, Boolean.parseBoolean(param));
                } else if ("Short".equalsIgnoreCase(typeName)) {
                    method.invoke(obj, Short.parseShort(param));
                } else if ("Character".equals(typeName) || "char".equals(typeName)) {
                    method.invoke(obj, param.toCharArray()[0]);
                } else {
                    //若數據格式爲 String 則沒必要轉換
                    method.invoke(obj, param);
                }
            }
            //不要忘記這句
            list.add(obj);
        }
        return list;
    }
    複製代碼

    接下來咱們改造 Person 類,添加幾個不一樣類型的數據,並加入 toString() 方法,供咱們測試使用。

    import java.util.Date;
    
    /** * * @author calmer * @since 2018/12/5 14:50 */
    public class Person {
        private Integer id;
        private String name;
        private Integer age;
        private String hobby;
        private String job;
        private String address;
        private Date birthday;
        private Character sex;
        private Long phone;
        private Boolean isWorked;
    
        @Override
        public String toString() {
            return "Person{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    ", hobby='" + hobby + '\'' +
                    ", job='" + job + '\'' +
                    ", address='" + address + '\'' +
                    ", birthday=" + birthday +
                    ", sex=" + sex +
                    ", phone=" + phone +
                    ", isWorked=" + isWorked +
                    '}';
        }
    
    
        public Long getPhone() {
            return phone;
        }
    
        public void setPhone(Long phone) {
            this.phone = phone;
        }
    
        public Boolean getWorked() {
            return isWorked;
        }
    
        public void setWorked(Boolean worked) {
            isWorked = worked;
        }
    
        public Character getSex() {
            return sex;
        }
    
        public void setSex(Character sex) {
            this.sex = sex;
        }
    
        public Date getBirthday() {
            return birthday;
        }
    
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getHobby() {
            return hobby;
        }
    
        public void setHobby(String hobby) {
            this.hobby = hobby;
        }
    
        public String getJob() {
            return job;
        }
    
        public void setJob(String job) {
            this.job = job;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    }
    
    複製代碼

    接下來是測試調用的代碼,咱們直接將導出與導入兩段代碼一塊兒執行。

    public static void main(String[] args) throws Exception {
        //生成數據
        List<Person> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            int num = i + 1;
            Person person = new Person();
            person.setId(num);
            person.setName("張三-"+(num));
            person.setAddress("花園路"+num+"號"+(int)Math.ceil(Math.random()*10)+"號樓");
            person.setAge(i+18);
            person.setHobby("洗臉刷牙打DOTA");
            person.setJob("程序員");
            person.setBirthday(new Date());
            person.setSex('男');
            person.setPhone(4536456498778789123L);
            person.setWorked(true);
            list.add(person);
        }
        //導出 Excel
        FileOutputStream out = new FileOutputStream("F:\\testXSSF.xlsx");
        ExcelFactory.createExcel(list,ExcelType.SXSSF).write(out);
        out.close();
        //導入 Excel
        FileInputStream in = new FileInputStream("F:\\testXSSF.xlsx");
        XSSFWorkbook workbook = new XSSFWorkbook(in);
        List<Person> personList = ExcelFactory.readExcel(workbook,Person.class);
        in.close();
        //遍歷結果
        for (Person person : personList) {
            System.out.println(person);
        }
    }
    複製代碼

    執行結果以下:

    image.png | left | 747x113

    image.png | left | 747x135

    功能已經基本實現,咱們此次再試一下在大數據導入的情景下,程序的耗時如何。咱們此次一樣適用 6W 條數據。結果以下

    image.png | left | 516x170

    這裏咱們能夠看到,導入操做佔用的內存和耗時,都比導出操做多不少。在導出的時候咱們知道 POI 在導出大數據量的時候提供了 SXSSF 的方式解決耗時和內存溢出問題,那麼在導入時是否是也會有某種方式能夠解決這個問題呢?

    使用 Sax 事件驅動解析

    關於這部分的代碼,能夠在網上找到許多,本次暫不討論。另外據說有一個 EasyExcel 挺好用的,有時間試一下。

    感悟

    經過此次探索,深知本身不足的地方還不少,原來寫代碼的時候考慮的太少,有關效率,內存使用等方面的問題在本身測試的時候是看不出來的,真正使用的時候這些問題纔會暴露出來,好比某項操做可能會致使用戶幾十秒甚至幾分鐘的等待,或者程序直接崩掉。 因此之後仍是要當心謹慎,對工具類的使用不能會用就夠,要儘可能的深刻研究。 道可頓悟,事需漸修。

    須知

相關文章
相關標籤/搜索