POI導出詳解

最爲常見的POI導出方式有3種:HSSF,XSSF,SXSSF

XSSFworkbook:操作Excel2007版本,擴展名爲xlsx,玩這個不如玩SXSSF

pom文件,下面那個fastjson是我做數據格式化的,可以不用

<dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>

實體類

package com.xx.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * @author aqi
 * DateTime: 2020/5/20 11:03 上午
 * Description: No Description
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Integer id;
    private String name;
    private String password;
    private Integer gender;
    private Boolean live;
    private String remarks;
    private Date createTime;
    private String other;
    private String msg1;
    private String msg2;
    private String msg3;

}

 下面是工具類

使用HSSF導出的工具類,不推薦使用,是2003版本之前的Excel,導出文件擴展名爲xls,這種方式在數據量不大的情況下也可以使用,速度也不會差很多,當數據量大的時候,每個sheet限制在6w條,會產生很多的sheet,並且導出的文件大小很大,效率相較於SXSSF稍微差點,如果是遺留項目,需要更改時HSSF轉SXSSF也非常的容易,代碼變化不大,可以放心技術迭代
1w條數據基本上在0.5s以內,文件大小在2.5MB左右

5w條數據基本上在3s左右,文件大小在11MB左右

package com.xx.utils;

import com.alibaba.fastjson.JSON;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;

/**
 * @author aqi
 * DateTime: 2020/5/28 9:17 上午
 * Description:
 *      使用HSSF進行Excel導出(不推薦使用)
 *          1.HSSF操作的是2003版本之前的Excel,擴展名是xls
 *          2.不希望方法調用的時候傳遞那麼多參數,想要修改的參數直接修改方法內的靜態參數就好了
 *          3.過多的樣式我就不diy了,實在是太多了,默認定義了一個我覺得還行的樣式,可以直接使用
 *      HSSF的缺陷:
 *          當導出數據超過65536條就會報錯,拋出這個異常,網上有很多解決方案,我比較推薦不使用這種方式導出  java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)
 *      HSSF的效率
 *          10次一組跑了10次:
 *              1w條數據基本上在0.5秒以內,文件大小在2.5MB左右,偶爾存在波動情況
 *              5w條數據基本上在3秒左右,文件大小在11MB左右
 *
 *
 */
public class ExcelUtils {
    /**
     * 表頭字體大小
     */
    private static String headerFontSize = "13";
    /**
     * 表頭字體樣式
     */
    private static String headerFontName = FontStyle.MicrosoftYahei.name;
    /**
     * 數據字體大小
     */
    private static String otherFontSize = "10";
    /**
     * 數據字體樣式
     */
    private static String otherFontName = FontStyle.MicrosoftYahei.name;
    /**
     * 單元格寬度
     */
    private static Integer width = 30;
    /**
     * sheet的名字
     */
    private static String sheetName = "sheetName";
    /**
     * 是否開啓表頭樣式,默認爲true,開啓
     */
    private static Boolean isOpeanHeaderStyle = true;
    /**
     * ##############是否開始其他數據樣式,默認爲false,關閉(不建議開啓,數據量大時影響性能)################
     */
    private static Boolean isOpeanOtherStyle = false;

    /**
     * @param keys        對象屬性對應中文名
     * @param columnNames 對象的屬性名
     * @param fileName    文件名
     * @param list        需要導出的json數據
     * @description 使用HSSFWorkBook導出數據, HSSF導出數據存在一些問題
     */
    public static void exportExcel(HttpServletResponse response, String[] keys, String[] columnNames, String fileName, List<Map<String, Object>> list) throws IOException {
        // 創建一個工作簿
        HSSFWorkbook wb = new HSSFWorkbook();
        // 創建一個sheet
        HSSFSheet sh = wb.createSheet(sheetName);
        // 創建Excel工作表第一行,設置表頭信息
        HSSFRow row0 = sh.createRow(0);
        for (int i = 0; i < keys.length; i++) {
            // 設置單元格寬度
            sh.setColumnWidth(i, 256 * width + 184);
            HSSFCell cell = row0.createCell(i);
            cell.setCellValue(keys[i]);
            // 是否開啓表頭樣式
            if (isOpeanHeaderStyle) {
                // 創建表頭樣式
                HSSFCellStyle headerStyle = setCellStyle(wb, headerFontSize, headerFontName, "header");
                cell.setCellStyle(headerStyle);
            }
        }

        for (int i = 0; i < list.size(); i++) {
            // 循環創建行
            HSSFRow row = sh.createRow(i + 1);
            // 給這行的每列寫入數據
            for (int j = 0; j < columnNames.length; j++) {
                HSSFCell cell = row.createCell(j);
                // 以這樣的方式取值,過濾掉不需要的字段
                String value = String.valueOf(list.get(i).get(columnNames[j]));
                cell.setCellValue(value);
                // 是否開始其他數據樣式
                if (isOpeanOtherStyle) {
                    // 設置數據樣式
                    HSSFCellStyle otherStyle = setCellStyle(wb, otherFontSize, otherFontName, "other");
                    cell.setCellStyle(otherStyle);
                }
            }
        }
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        // 這個操作也非常的耗時,暫時不知道和什麼有關,應該該和文件的大小有關
        wb.write(response.getOutputStream());
    }

    /**
     * @param wb       工作簿
     * @param fontSize 字體大小
     * @param fontName 字體名稱
     * @return 工作簿樣式
     * @description 設置Excel樣式
     */
    private static HSSFCellStyle setCellStyle(HSSFWorkbook wb, String fontSize, String fontName, String boo) {
        // 創建自定義樣式類
        HSSFCellStyle style = wb.createCellStyle();
        // 創建自定義字體類
        HSSFFont font = wb.createFont();
        // 設置字體樣式
        font.setFontName(fontName);
        // 設置字體大小
        font.setFontHeightInPoints(Short.parseShort(fontSize));
        // 我這個版本的POI沒找到網上的HSSFCellStyle
        // 設置對齊方式
        style.setAlignment(HorizontalAlignment.CENTER);
        // 數據內容設置邊框實在太醜,容易看瞎眼睛,我幫你們去掉了
        if ("header".equals(boo)) {
            // 設置邊框
            style.setBorderBottom(BorderStyle.MEDIUM);
            style.setBorderLeft(BorderStyle.MEDIUM);
            style.setBorderRight(BorderStyle.MEDIUM);
            style.setBorderTop(BorderStyle.MEDIUM);
            // 表頭字體加粗
            font.setBold(true);
        }
        style.setFont(font);
        return style;
    }

    /**
     * 格式化數據(我發現這個操作非常的消耗時間,儘量不要使用到這個數據轉化,如果是List<Map>就直接傳,如果是Json稍微改一下上面的工具類,List<Bean>好像沒什麼比較好的處理手段)
     *
     * @param s json數據
     * @return 裝換成List集合的數據
     */
    public static List<Map<String, Object>> toList(String s) {
        List<Map<String, Object>> list = (List) JSON.parse(s);
        return list;
    }

    /**
     * 找了半天也沒找到可以diy的類,我自己寫個吧
     */
    enum FontStyle {
        // 微軟雅黑
        MicrosoftYahei("微軟雅黑"),
        // 宋體
        TimesNewRoman("宋體"),
        // 楷體
        Italics("楷體"),
        // 幼圓
        YoungRound("幼圓");

        private String name;

        FontStyle(String name) {
            this.name = name;
        }
    }
}

使用SXSSF導出的工具類,推薦使用,SXSSF可用於大數據量的導出,更加推薦使用這種方式,效率更高,每個sheet可以容納的數據更多,還可以避免內存溢出的問題,每個sheet可以存儲100w以上的數據

1w條數據基本上在0.5s以內,文件大小在700KB左右

5w條數據基本上在1.7s以內,文件大小在3.5MB左右

100w條數據基本上在35s左右,文件大小在70MB左右

200w條數據基本上在85s左右,文件大小在140MB左右(做了多sheet處理,如果項目經理要一次性導出還要在5s內的話,你就把他鯊了吧)

 

                                                             這個是在導出200w數據時的電腦性能監控

package com.xx.utils;

import com.alibaba.fastjson.JSON;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;

/**
 * @author aqi
 * DateTime: 2020/5/28 11:50 上午
 * Description:
 *      使用SXSSF進行Excel導出(推薦使用)
 *          1.SXSSF用於大數據量導出,擴展名是xlsx
 *          2.不希望方法調用的時候傳遞那麼多參數,想要修改的參數直接修改方法內的靜態參數就好了
 *          3.過多的樣式我就不diy了,實在是太多了,默認定義了一個我覺得還行的樣式,可以直接使用
 *      SXSSF的缺陷:
 *          當導出數據超過1048576條就會報錯,拋出這個異常,因爲每個Sheet最多隻能存1048576條數據,這時候需要將數據寫到新的Sheet中,工具類已優化  java.lang.IllegalArgumentException: Invalid row number (1048576) outside allowable range (0..1048575)
 *      SXSSF的效率
 *          10次一組跑了10次:
 *              1w條數據基本上在0.5秒以內,文件大小在700KB左右
 *              5w條數據基本上在1.7秒左右,文件大小在3.5MB左右
 *              100w條數據基本上在35秒左右,文件大小在70MB左右(雖然內存沒有溢出,但是cpu資源吃的厲害)
 *              200W條數據基本上在85秒左右,文件大小在140MB左右(做了多sheet處理,如果項目經理要一次性導出還要在5s內的話,你就把他鯊了吧)
 *
 *
 */
public class ExcelUtils {
    /**
     * 表頭字體大小
     */
    private static String headerFontSize = "13";
    /**
     * 表頭字體樣式
     */
    private static String headerFontName = FontStyle.MicrosoftYahei.name;
    /**
     * 數據字體大小
     */
    private static String otherFontSize = "10";
    /**
     * 數據字體樣式
     */
    private static String otherFontName = FontStyle.MicrosoftYahei.name;
    /**
     * 單元格寬度
     */
    private static Integer width = 30;
    /**
     * sheet的名字
     */
    private static String sheetName = "sheetName";
    /**
     * 每個sheet存放的數據量
     */
    private static Integer sheetLength = 1000000;
    /**
     * 是否開啓表頭樣式,默認爲true,開啓
     */
    private static Boolean isOpeanHeaderStyle = true;
    /**
     * ##############是否開始其他數據樣式,默認爲false,關閉(不建議開啓,數據量大時影響性能)################
     */
    private static Boolean isOpeanOtherStyle = false;

    /**
     * @param keys        對象屬性對應中文名
     * @param columnNames 對象的屬性名
     * @param fileName    文件名
     * @param list        需要導出的json數據
     * @description 使用SXSSFWorkBook導出數據
     */
    public static void exportExcel(HttpServletResponse response, String[] keys, String[] columnNames, String fileName, List<Map<String, Object>> list) throws IOException {
        // 創建一個工作簿,每寫100條數據就刷新數據出緩存,避免內存溢出
        SXSSFWorkbook wb = new SXSSFWorkbook(100);
        // 傳入數據的大小
        int listSize = list.size();
        // 創建一個sheet
        SXSSFSheet sh = wb.createSheet(sheetName);
        // 設置這個sheet表頭信息和樣式
        setHeaderStyle(sh, keys, wb);
        // 用於計數,每100w時重新開始創建行
        int temp = 0;
        // 用於創建不同的sheetName
        int sheetNameEnd = 0;
        // 這個二重循環不知道有沒有優化的空間了
        for (int i = 0; i < listSize; i++, temp++) {
            // 每100w重新創建一個新的sheet
            if (i % sheetLength == 0 && i != 0) {
                sheetNameEnd++;
                // 創建新的sheet
                sh = wb.createSheet(sheetName + sheetNameEnd);
                // 新的sheet設置新的單元格寬度
                setHeaderStyle(sh, keys, wb);
                temp = 0;
            }
            // 循環創建行
            SXSSFRow row = sh.createRow(temp + 1);
            // 給這行的每列寫入數據
            for (int j = 0; j < columnNames.length; j++) {
                SXSSFCell cell = row.createCell(j);
                // 以這樣的方式取值,過濾掉不需要的字段
                String value = String.valueOf(list.get(i).get(columnNames[j]));
                cell.setCellValue(value);
                // 是否開始其他數據樣式
                if (isOpeanOtherStyle) {
                    // 設置數據樣式
                    CellStyle otherStyle = setCellStyle(wb, otherFontSize, otherFontName, "other");
                    cell.setCellStyle(otherStyle);
                }
            }
        }
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        // 這個操作也非常的耗時,應該該和文件的大小有關,後續看看能不能優化
        wb.write(response.getOutputStream());
    }

    /**
     * 設置表頭樣式,在大數據情況下每個sheet都要執行一次,所以抽出出來了
     * @param sh sheet
     * @param keys 對象屬性對應中文名
     * @param wb 工作簿
     */
    private static void setHeaderStyle (SXSSFSheet sh, String[] keys, SXSSFWorkbook wb) {
        // 創建Excel工作表第一行,設置表頭信息
        SXSSFRow row0 = sh.createRow(0);
        for (int i = 0; i < keys.length; i++) {
            // 設置單元格寬度
            sh.setColumnWidth(i, 256 * width + 184);
            SXSSFCell cell = row0.createCell(i);
            cell.setCellValue(keys[i]);
            // 是否開啓表頭樣式
            if (isOpeanHeaderStyle) {
                // 創建表頭樣式
                CellStyle headerStyle = setCellStyle(wb, headerFontSize, headerFontName, "header");
                cell.setCellStyle(headerStyle);
            }
        }
    }

    /**
     * @param wb       工作簿
     * @param fontSize 字體大小
     * @param fontName 字體名稱
     * @return 工作簿樣式
     * @description 設置Excel樣式
     */
    private static CellStyle setCellStyle(SXSSFWorkbook wb, String fontSize, String fontName, String boo) {
        // 創建自定義樣式類
        CellStyle style = wb.createCellStyle();
        // 創建自定義字體類
        Font font = wb.createFont();
        // 設置字體樣式
        font.setFontName(fontName);
        // 設置字體大小
        font.setFontHeightInPoints(Short.parseShort(fontSize));
        // 我這個版本的POI沒找到網上的HSSFCellStyle
        // 設置對齊方式
        style.setAlignment(HorizontalAlignment.CENTER);
        // 數據內容設置邊框實在太醜,容易看瞎眼睛,我幫你們去掉了
        if ("header".equals(boo)) {
            // 設置邊框
            style.setBorderBottom(BorderStyle.MEDIUM);
            style.setBorderLeft(BorderStyle.MEDIUM);
            style.setBorderRight(BorderStyle.MEDIUM);
            style.setBorderTop(BorderStyle.MEDIUM);
            // 表頭字體加粗
            font.setBold(true);
        }
        style.setFont(font);
        return style;
    }

    /**
     * 格式化數據(我發現這個操作非常的消耗時間,儘量不要使用到這個數據轉化,如果是List<Map>就直接傳,如果是Json稍微改一下上面的工具類,List<Bean>好像沒什麼比較好的處理手段)
     * 數據量達到200w的時候堆內存直接就爆了,我錯了我錯了,數量達千萬別用     java.lang.OutOfMemoryError: Java heap space
     *
     * @param s json數據
     * @return 轉換成List集合的數據
     */
    public static List<Map<String, Object>> toList(String s) {
        List<Map<String, Object>> list = (List) JSON.parse(s);
        return list;
    }

    /**
     * 找了半天也沒找到可以diy的類,我自己寫個吧
     */
    enum FontStyle {
        // 微軟雅黑
        MicrosoftYahei("微軟雅黑"),
        // 宋體
        TimesNewRoman("宋體"),
        // 楷體
        Italics("楷體"),
        // 幼圓
        YoungRound("幼圓");

        private String name;

        FontStyle(String name) {
            this.name = name;
        }
    }
}

測試接口

package com.xx.controller;

import com.alibaba.fastjson.JSON;
import com.xx.entity.User;
import com.xx.utils.ExcelUtils;
import com.xx.utils.ExcelUtils1;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * @author aqi
 * DateTime: 2020/5/27 2:02 下午
 * Description: poi導出
 *              HSSFWorkBook,
 *              XSSFworkbook,
 *              SXSSFworkbook
 */
@RestController
public class POI {

    /**
     * 控制數據的大小
     */
    private static Integer count = 2000000;
    private static List<User> userList = new ArrayList<>();
    private static ArrayList<Map<String, Object>> mapsList = new ArrayList<>();

    /**
     * 初始化數據
     */
    static {
        for (int i = 0; i < count; i++) {
            userList.add(new User(i, "張三" + i, UUID.randomUUID().toString().replaceAll("-", ""), i % 2, i % 2 == 0, "這個數據是用戶的備註信息,我想把這個數據弄長一點,現在這個長度感覺還不太行,應該要再長一點,現在這個長度我感覺差不多了,就這樣吧,但是有時候導出的時候會導出很長的字段,不知道對導出的效率影響大不大,現在這個長度應該是夠了", new Date(), "這裏存放了一些其他字段", "", "其他2個就不存值了", ""));

            HashMap<String, Object> map = new HashMap<>(11);
            map.put("id", i);
            map.put("name", "張三" + i);
            map.put("password", UUID.randomUUID().toString().replaceAll("-", ""));
            map.put("gender", i % 2);
            map.put("live", i % 2 == 0);
            map.put("remarks", "這個數據是用戶的備註信息,我想把這個數據弄長一點,現在這個長度感覺還不太行,應該要再長一點,現在這個長度我感覺差不多了,就這樣吧,但是有時候導出的時候會導出很長的字段,不知道對導出的效率影響大不大,現在這個長度應該是夠了");
            map.put("createTime", new Date());
            map.put("other", "這裏存放了一些其他字段");
            map.put("msg1", "");
            map.put("msg2", "其他2個就不存值了");
            map.put("msg3", "");
            mapsList.add(map);

        }
    }

    @GetMapping("/getExcel")
    public void getExcel(HttpServletResponse response) throws IOException {
        String[] keys = {"序號", "姓名", "密碼", "性別", "是否激活", "備註信息", "創建時間", "其他", "備註字段1", "備用字段2", "備用字段3"};
        String[] columnNames = {"id", "name", "password", "gender", "live", "remarks", "createTime", "other", "msg1", "msg2", "msg3"};
        String fileName = "demo.xlsx";

//        這個數據格式化在數據量很大的情況下,會導致堆內存溢出
//        String s = JSON.toJSONString(userList);
//        List<Map<String, Object>> maps = ExcelUtils.toList(s);


//        ExcelUtils.exportExcel(response, keys, columnNames, fileName, mapsList);
    }

}

 

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