EasyExcel处理大数据量分段导出

3 篇文章 0 订阅
2 篇文章 0 订阅

1.场景说明

在实际生产场景中会存在同时有多人操作巨量数据导出,如果是采取的先把所有符合筛选条件的数据查询出来,再挨个写入Excel的方式,可能会产生CPU飙高到100%,内存被占满,系统罢工的情况,因此需要采用其他方案解决此问题。本人采取的方案是分段导出策略,假设一共50万数据需要导出,每查询到1000条数据就写入Excel,直到查询完50万数据并写入Excel为止。需要注意的是,生成的File文件及其他对象需要及时释放内存,以给其他操作的人腾出空间。

3.基本实现

3.1添加依赖

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

3.2 代码示例

package com.jdq.mall.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import lombok.Data;
import org.apache.poi.ss.usermodel.*;

import java.util.*;

/**
 * 分段导出
 */
public class BatchExportEasyExcelUtil {

    public static void batchExportToExcel() {
        int pageNum = 1;
        int pageSize = 10;
        //文件名 
        String fileName = "D:\\Users\\SuperMan\\Desktop\\test\\user.xlsx";
        //1、模拟数据
        List<Map> data = simulatedData();
        //计算模拟数据的总页数
        int pageTotal = (data.size() % pageSize) > 0 ? (data.size() / pageSize) + 1 : (data.size() / pageSize);
        ExcelWriter excelWriter = null;
        // 2、定义动态表头
        List<List<String>> headList = new ArrayList<>();//字段中文说明
        //List<String> headCodeList = new ArrayList<>();//字段编码
        //配置:在实际项目中,可以是枚举类,可以是数据库表里的数据,或者其他数据来源
        Map<String, String> fieldMap = new HashMap<>();
        fieldMap.put("idCard", "身份证号码");
        fieldMap.put("id", "主键");
        fieldMap.put("name", "姓名");
        fieldMap.put("address", "所在地址");
        List<String> finalFieldList = new ArrayList<>(fieldMap.keySet());
        //中文表头
        for (String field : finalFieldList) {
            headList.add(Arrays.asList(fieldMap.get(field)));
            //headCodeList.add(field);
        }
        try {
            //3、设置样式
            excelWriter = EasyExcel.write(fileName, Map.class).build();
            //3.1 表头样式
            WriteCellStyle headStyle = new WriteCellStyle();
            WriteFont font = new WriteFont();
            font.setFontHeightInPoints((short) 12); // 设置字体大小为12
            font.setBold(false);
            headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            headStyle.setVerticalAlignment(VerticalAlignment.CENTER);
            headStyle.setWriteFont(font);
            headStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
            //3.2 单元格内容样式
            WriteCellStyle contentStyle = new WriteCellStyle();
            WriteFont font2 = new WriteFont();
            font2.setBold(false);
            font2.setFontHeightInPoints((short) 11); // 设置字体大小为11
            contentStyle.setWriteFont(font2);
            contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
            //4、创建工作表,表头和列表是自适应的。
            WriteSheet writeSheet = EasyExcel
                    .writerSheet("用户信息")
                    .head(headList)
                    .registerWriteHandler(new HorizontalCellStyleStrategy(headStyle, contentStyle))
                    .registerWriteHandler(new CustomCellWriteWidthConfig()) /*自适应列宽*/
                    .build();
            long startTime = System.currentTimeMillis();
            while (true) {
                System.out.println("数据导出开始,第" + pageNum + "页,总页数:" + pageTotal);
                // 5、模拟分页查询方法
                List<Map> pageDataList = queryUsersByPage(pageNum, pageSize, data);
                if (pageDataList == null || pageDataList.isEmpty()) {
                    // 查询结果为空时,终止循环
                    System.out.println("查询结果为空,终止循环,总用时:" + (System.currentTimeMillis() - startTime) / 1000 + "秒");
                    break;
                }
                //6、处理分页查询的数据,将获取的数据集处理成与Excel单元格表头匹配的数据。
                //即:单元格表头只有一行,而数据有多行,将数据集中的每条数据转换为与单元格表头匹配的单行数据
                List<List<Object>> dataList = new ArrayList<>();
                for (Map map : pageDataList) {
                    List<Object> rowDatas = new ArrayList<>();
                    for (String field : finalFieldList) {
                        String value = "";
                        if (Objects.nonNull(map.get(field))) {
                            value = String.valueOf(map.get(field));
                        }
                        rowDatas.add(value);
                    }
                    dataList.add(rowDatas);
                }
                excelWriter.write(dataList, writeSheet);
                // 记录日志:准备查询下一页
                System.out.println("第" + pageNum + "页数据写入完成,总页数:" + pageTotal);
                pageNum++;
            }
            //return new File(fileName);
        } finally {
            if (excelWriter != null) {
                // 关闭Writer释放资源
                excelWriter.finish();
            }
        }
    }

    /**
     * 模拟数据(DB存储)
     */
    private static List<Map> simulatedData() {
        List<Map> data = new ArrayList<>();
        for (int i = 1; i <= 100000; i++) {
            Map userMap = new HashMap();
            userMap.put("id", i);
            userMap.put("name", "姓名" + i);
            userMap.put("address", "所在地址所在地址所在地址" + i);
            data.add(userMap);
        }
        return data;
    }

    /**
     * 模拟分页查询
     *
     * @param pageNum  页码
     * @param pageSize 页大小
     * @param data     数据
     */
    private static List<Map> queryUsersByPage(int pageNum, int pageSize, List<Map> data) {
        // 分页查询逻辑实现,此处以伪代码表示
        List<List<Map>> lists = splitList(data, pageSize);
        if (lists.size() < pageNum) {
            return null;
        }
        // 返回查询结果
        return lists.get(pageNum - 1);
    }

    public static <T> List<List<T>> splitList(List<T> alllist, int groupSize) {
        int length = alllist.size();
        // 计算可以分成多少组
        int num = (length + groupSize - 1) / groupSize;
        List<List<T>> newList = new ArrayList(num);
        for (int i = 0; i < num; i++) {
            // 开始位置
            int fromIndex = i * groupSize;
            // 结束位置
            int toIndex = (i + 1) * groupSize < length ? (i + 1) * groupSize : length;
            newList.add(alllist.subList(fromIndex, toIndex));
        }
        return newList;
    }

    public static void main(String[] args) {
        batchExportToExcel();
    }

}

@Data
class DataModel {
    //    @ExcelProperty("用户ID")
    //    @ColumnWidth(15) // 设置列宽为15字符宽度
    //    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40) // 设置头部样式,比如背景颜色
    //    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    //    @HeadFontStyle(bold = BooleanEnum.TRUE, fontHeightInPoints = 12) // 设置头部字体样式
    private int id;

    //    @ExcelProperty("姓名")
    //    @ColumnWidth(15) // 设置列宽为15字符宽度
    //    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    //    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40) // 设置头部样式,比如背景颜色
    //    @HeadFontStyle(bold = BooleanEnum.TRUE, fontHeightInPoints = 12) // 设置头部字体样式
    private String name;

    //    @ExcelProperty("身份证")
    //    @ColumnWidth(35) // 设置列宽为15字符宽度
    //    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    //    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40) // 设置头部样式,比如背景颜色
    //    @HeadFontStyle(bold = BooleanEnum.TRUE, fontHeightInPoints = 12) // 设置头部字体样式
    //    private String idCard;

    //    @ExcelProperty("地址")
    //    @ColumnWidth(50) // 设置列宽为15字符宽度
    //    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    //    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40) // 设置头部样式,比如背景颜色
    //    @HeadFontStyle(bold = BooleanEnum.TRUE, fontHeightInPoints = 12) // 设置头部字体样式
    private String address;
}

/**
 * 自适应列宽计算策略
 */
class CustomCellWriteWidthConfig extends AbstractColumnWidthStyleStrategy {
    /**
     * 工作表缓存
     */
    private final Map<Integer, Map<Integer, Integer>> CACHE = new HashMap<>();

    @Override
    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer integer, Boolean isHead) {
        //若表头、数据不为空,则需要计算列宽
        boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
        if (needSetWidth) {
            //每个页签缓存最大的
            Map<Integer, Integer> maxColumnWidthMap = CACHE.computeIfAbsent(writeSheetHolder.getSheetNo(), k -> new HashMap<>());
            Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
            // 单元格文本长度大于60换行
            if (columnWidth >= 0) {
                if (columnWidth > 60) {
                    columnWidth = 60;
                }
                Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
                //1.首次计算列宽
                //2.当前计算列宽大于缓存的列宽,及时更新最大列宽,并设置到工作表中
                if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                    maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
                    Sheet sheet = writeSheetHolder.getSheet();
                    sheet.setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
                }
            }
        }
    }

    /**
     * 计算数据长度
     *
     * @param cellDataList 数据源
     * @param cell         单元格
     * @param isHead       是否是表头
     */
    private Integer dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData<?> cellData = cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return -1;
            } else {
                switch (type) {
                    case STRING:
                        // 换行符(数据需要提前解析好)
                        int index = cellData.getStringValue().indexOf("\n");
                        return index != -1 ?
                                cellData.getStringValue().substring(0, index).getBytes().length + 1 : cellData.getStringValue().getBytes().length + 1;
                    case BOOLEAN:
                        return cellData.getBooleanValue().toString().getBytes().length;
                    case NUMBER:
                        return cellData.getNumberValue().toString().getBytes().length;
                    default:
                        return -1;
                }
            }
        }
    }
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值