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);
    }

}

猜你喜欢

转载自blog.csdn.net/progammer10086/article/details/106401466
今日推荐