玩转EasyExcel,看这一篇就够了!!(合并导入 自定义导出 动态表头 合并单元格)

1,136 阅读9分钟

曾经,Excel是Java程序员的噩梦——内存爆炸、格式错乱、代码写到手抽筋。直到EasyExcel横空出世,它像一位Excel界的'扫地僧',默默把内存消耗从'火箭发射'降级到'自行车爬坡'。现在,我们终于可以一边喝着咖啡,一边优雅地处理导入导出了。

处理Excel就像吃火锅:传统工具是'一口闷',结果烫得胃疼(内存溢出);EasyExcel是'涮着吃',边吃边处理(流式解析),既吃得爽(效率高),又不会撑坏(内存省)。现在,你可以优雅地'涮'完整个数据湖了。

废话不多说,上代码直接撸啊撸

引入依赖:

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

普通导出(粘贴即用):

定义model

有的gege喜欢定义 index,我是不喜欢的,改个顺序,都得撸一遍,直接默认顺序完事。


@ColumnWidth(20)
@Data
public class FcBaseInfoModel {

    @ExcelProperty(value = "字段1")
    private String colunmOne;

    @ExcelProperty(value = "造型面名称")
    private String colunmTwo;

    @ExcelProperty(value = "项目")
    private String colunmThree;
    
}

导出到 response 代码

public static void exportExcel(HttpServletResponse response, String fileName, String sheetName, List<?> list, Class<?> pojoClass) {
    if (StringUtils.isBlank(fileName)) {
        fileName = DateUtils.format(new Date());
    }
    try {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");

        EasyExcel.write(response.getOutputStream(), pojoClass)
                .registerConverter(new LongStringConverter())
                .registerWriteHandler(new HorizontalCellStyleStrategy(null, getWriteCellStyle()))
                .sheet(sheetName).doWrite(list);
    } catch (Exception e) {
        throw new ServiceException("导出文件失败:", e.getMessage());
    }

}

单个Sheet页导出到本地(粘贴即用):

通常运用在导出到本地,然后打包什么的,需要中转的场景。

/**
 * excel单个sheet导出
 */
public static <T> void export(String path, String fileName, Class<T> exportType, List<T> list) {
    //创建文件
    String filePath = getFilePath(path, fileName);
    String sheetName = fileName.replace(".xlsx", "").replace(".xls", "");
    EasyExcel.write(filePath, exportType).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet(sheetName).doWrite(list);
}

多Sheet页导出到本地(粘贴即用):

和上面的一样导出到本地,然后打包什么的,需要中转的场景。
三个必须需要 **一 一 对应 **

tClassList model类型
sheetNameList sheet名称
resultList 数据集

public static void exportMultipleSheet(String path, String fileName, List<Class<?>> tClassList, List<String> sheetNameList, List<List<Object>> resultList) {
    //创建文件
    String filePath = getFilePath(path, fileName);
    File file = new File(filePath);
    try {
        FileOutputStream output = new FileOutputStream(file);
        ExcelWriter excelWriter = EasyExcel.write(output).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();
        for (int i = 0; i < sheetNameList.size(); i++) {
            WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetNameList.get(i)).head(tClassList.get(i)).build();
            excelWriter.write(resultList.get(i), writeSheet);
        }
        //关闭流
        excelWriter.finish();
        output.flush();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public static String getFilePath(String path, String fileName) {
    //创建文件夹
    File fileOld = new File(path);
    if (!fileOld.exists()) {
        try {
            FileUtil.mkdir(fileOld);
        } catch (Exception e) {
            throw new ServiceException("创建文件夹失败");
        }
    }
    //写入文件绝对地址
    return String.format("%s/%s", path, fileName);
}

自定义表头

第一步:构建表头

@Data
public class EasyExcelTitleInfo {

    // 索引 即列顺序
    private Integer index;


    // 标题 支持多级
    private List<String> title;


    // 对应返回数据的字段名称
    private String columnName;

}

第二步:引入工具类方法

public static void exportExcelCustomizationHead(HttpServletResponse response, String fileName, String sheetName, List<?> list, List<EasyExcelTitleInfo> titleInfoList) {
    if (CollectionUtils.isEmpty(titleInfoList)) {
        throw new ServiceException("标题不能为空");
    }

    if (StringUtils.isBlank(fileName)) {
        fileName = DateUtils.format(new Date());
    }
    try {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");

        EasyExcel.write(response.getOutputStream())
                // excel表头处理
                .head(getHead(titleInfoList))
                .registerConverter(new LongStringConverter())
                // 这里定义统一列宽
                .registerWriteHandler(new AbstractColumnWidthStyleStrategy() {
                    @Override
                    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
                        Sheet sheet = writeSheetHolder.getSheet();
                        sheet.setColumnWidth(cell.getColumnIndex(), 5120);
                    }
                })
                // 样式定义
                .registerWriteHandler(new HorizontalCellStyleStrategy(null, getWriteCellStyle()))
                .sheet(sheetName)
                // excel数据处理
                .doWrite(getDataList(list, titleInfoList));

    } catch (Exception e) {
        throw new ServiceException("导出文件失败:", e.getMessage());
    }

}
// 获取表头
private static List<List<String>> getHead(List<EasyExcelTitleInfo> titleInfoList) {
    List<List<String>> result = Lists.newArrayList();
    titleInfoList.stream().sorted(Comparator.comparingInt(EasyExcelTitleInfo::getIndex))
            .forEach(title -> {
                List<String> currHead = Lists.newArrayList();
                currHead.addAll(title.getTitle());
                result.add(currHead);
            });
    return result;
}
// 转换数据
private static List<Map<Integer, String>> getDataList(List<?> list, List<EasyExcelTitleInfo> titleInfoList) {
    List<Map<Integer, String>> dataList = Lists.newArrayList();

    if (CollectionUtils.isEmpty(list)) {
        return dataList;
    }
    List<String> columnList = Lists.newArrayList();
    titleInfoList.stream().sorted(Comparator.comparingInt(EasyExcelTitleInfo::getIndex))
            .forEach(x -> columnList.add(x.getColumnName()));
    list.forEach(x -> {
        Map<String, Object> map = BeanUtil.beanToMap(x);
        Map<Integer, String> data = Maps.newLinkedHashMap();
        for (int i = 0; i < columnList.size(); i++) {
            String column = columnList.get(i);
            if (map.containsKey(column)) {
                Object o = map.get(column);
                if (null == o) {
                    data.put(i, "");
                } else if (o instanceof String) {
                    data.put(i, o.toString());
                } else if (o instanceof Date) {
                    data.put(i, DateUtil.formatDateTime((Date) o));
                } else {
                    data.put(i, o.toString());
                }
            } else {
                data.put(i, "");
            }
        }
        dataList.add(data);
    });

    return dataList;
}
// 定义导出数据样式
private static WriteCellStyle getWriteCellStyle() {
    WriteCellStyle writeCellStyle = new WriteCellStyle();
    writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
    writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
    writeCellStyle.setBorderTop(BorderStyle.THIN);
    writeCellStyle.setBorderLeft(BorderStyle.THIN);
    writeCellStyle.setBorderRight(BorderStyle.THIN);
    writeCellStyle.setBorderBottom(BorderStyle.THIN);
    return writeCellStyle;
}

导入

引入监听类

(此方法是为了解决读取合并单元格时,取值不统一的问题,另外去除整行都为空的数据)


public class EasyExcelBaseListener<T> extends AnalysisEventListener<T> {

    private final static Log log = LogFactory.getLog(EasyExcelBaseListener.class);

    /**
     * 表头
     */
    private Map<Integer, String> headMap;

    /**
     * 批量操作的数据
     */
    private List<T> dataList = new ArrayList<>();

    /**
     * 动态获取表行数
     */
    private int headRowNum = 0;

    /**
     * 获取所有合并单元格信息
     */
    private final List<CellExtra> cellExtraList = new ArrayList<>();

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        this.headMap = headMap;
        headRowNum++;
    }

    /**
     * 每次读取完一条数据触发的方法
     */
    @Override
    public void invoke(T t, AnalysisContext context) {
        dataList.add(t);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("读取excel完成");

        //读取完成 填充合并过的单元格
        if (CollectionUtils.isNotEmpty(cellExtraList)) {
            mergeExcelData(dataList, cellExtraList, headRowNum);
        }
    }


    public Map<Integer, String> getHeadMap() {
        return headMap;
    }

    public void setHeadMap(Map<Integer, String> headMap) {
        this.headMap = headMap;
    }

    public List<T> getDataList() {
        // 移除全部为空的行
        if (CollectionUtils.isNotEmpty(dataList)) {
            try {
                for (int i = dataList.size() - 1; i >= 0; i--) {
                    T t = dataList.get(i);

                    List<Field> fields = Arrays.stream(t.getClass().getDeclaredFields())
                            .filter(f -> f.isAnnotationPresent(ExcelProperty.class))
                            .collect(Collectors.toList());

                    boolean lineAllNull = Boolean.TRUE;
                    for (Field field : fields) {
                        field.setAccessible(true);
                        Object o = field.get(t);
                        if (!ObjectUtil.isNull(o)) {
                            lineAllNull = Boolean.FALSE;
                            break;
                        }
                    }
                    if (lineAllNull) {
                        dataList.remove(i);
                    } else {
                        break;
                    }
                }
            } catch (Exception e) {
                throw new ServiceException("读取数据失败");
            }
        }
        return dataList;
    }

    public void setDataList(List<T> dataList) {
        this.dataList = dataList;
    }


    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        CellExtraTypeEnum type = extra.getType();
        if (type == CellExtraTypeEnum.MERGE) {
            if (extra.getRowIndex() >= headRowNum) {
                cellExtraList.add(extra);
            }
        }
    }

    /**
     * 处理合并单元格数据,所有行以第一个为准
     */
    private void mergeExcelData(List<T> excelDataList, List<CellExtra> cellExtraList, int headRowNum) {
        cellExtraList.forEach(cellExtra -> {
            int firstRowIndex = cellExtra.getFirstRowIndex() - headRowNum;
            int lastRowIndex = cellExtra.getLastRowIndex() - headRowNum;
            int firstColumnIndex = cellExtra.getFirstColumnIndex();
            int lastColumnIndex = cellExtra.getLastColumnIndex();
            //获取初始值 合并单元格左上角的值
            Object initValue = getInitValueFromList(firstRowIndex, firstColumnIndex, excelDataList);
            //设置值 把合并单元格左上角的值 设置到合并区域的每一个单元格
            for (int i = firstRowIndex; i <= lastRowIndex; i++) {
                for (int j = firstColumnIndex; j <= lastColumnIndex; j++) {
                    setInitValueToList(initValue, i, j, excelDataList);
                }
            }
        });
    }


    /**
     * 设置值 把合并单元格左上角的值 设置到合并区域的每一个单元格
     */
    private void setInitValueToList(Object filedValue, Integer rowIndex, Integer columnIndex, List<T> data) {
        T object = data.get(rowIndex);

        for (Field field : object.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            if (annotation != null) {
                if (annotation.value()[annotation.value().length - 1].equals(headMap.get(columnIndex))) {
                    try {
                        field.set(object, filedValue);
                        break;
                    } catch (IllegalAccessException e) {
                        log.error("设置合并单元格的值异常:{}", e);
                    }
                }
            }
        }
    }

    /**
     * 获取初始值 合并单元格左上角的值
     */
    private Object getInitValueFromList(Integer firstRowIndex, Integer firstColumnIndex, List<T> data) {
        Object filedValue = null;
        T object = data.get(firstRowIndex);
        for (Field field : object.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            if (annotation != null) {
                if (annotation.value()[annotation.value().length - 1].equals(headMap.get(firstColumnIndex))) {
                    try {
                        filedValue = field.get(object);
                        break;
                    } catch (IllegalAccessException e) {
                        log.error("设置合并单元格的值异常:{}", e);
                    }
                }
            }
        }
        return filedValue;
    }

}

导入--读取数据

(此方法附带校验表头功能)


public static <T> List<T> readExcel(MultipartFile multipartFile, Class<T> tClass) {
    EasyExcelBaseListener<T> listener = new EasyExcelBaseListener<>();
    try {
        readExcel(multipartFile, tClass, listener);
    } catch (Exception e) {
        throw new ServiceException(e.getMessage());
    }
    List<T> dataList = listener.getDataList();
    Map<Integer, String> headMap = listener.getHeadMap();
    List<String> modelHeadList = excelHeadData(tClass);

    for (int i = 0; i < modelHeadList.size(); i++) {
        if (!headMap.containsKey(i) || !StringUtils.equals(headMap.get(i), modelHeadList.get(i))) {
            throw new ServiceException("表头校验:导入模版不正确");
        }
    }

    if (CollectionUtils.isEmpty(dataList)) {
        throw new ServiceException("导入数据为空,请检查导入文件");
    }
    return dataList;
}
public static void readExcel(MultipartFile file, Class clazz, AnalysisEventListener listener) throws Exception {
    checkFile(file);
    InputStream inputStream = null;
    try {
        inputStream = new BufferedInputStream(file.getInputStream());
        // 校验表头
        checkTableTitle(file, clazz);

        // 解析每行结果在listener中处理
        EasyExcelFactory.read(file.getInputStream(), clazz, listener)
                .extraRead(CellExtraTypeEnum.MERGE)
                .sheet().doRead();
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
/**
 * 校验文件
 */
private static void checkFile(MultipartFile file) {
    //判断文件是否存在
    if (null == file) {
        throw new ServiceException("文件不存在!");
    }
    String fileName = file.getOriginalFilename();
    //判断文件是否是excel文件
    if (StringUtils.isNotBlank(fileName) && !fileName.endsWith(TYPE_OF_XLS) && !fileName.endsWith(TYPE_OF_XLSX)) {
        throw new ServiceException(fileName + "不是excel文件");
    }
}

private static <T> List<String> excelHeadData(Class<T> clazz) {
    // 获取所有的私有属性
    Field[] fields = clazz.getDeclaredFields();
    // 遍历所有属性
    List<String> excelHeadData = new ArrayList<>();
    for (Field field : fields) {
        ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
        if (null != annotation) {
            excelHeadData.add(annotation.value()[0]);
        }
    }
    return excelHeadData;
}
public static void checkTableTitle(MultipartFile file, Class clazz) throws Exception {
    ByteArrayInputStream is = new ByteArrayInputStream(file.getBytes());
    ExcelReader reader = ExcelUtil.getReader(is);
    List<List<Object>> read = reader.read();
    List<Object> importTitleList = read.get(0);
    if (CollectionUtils.isEmpty(importTitleList)) {
        throw new ServiceException("导入表头为空");
    }

    Field[] declaredFields = clazz.getDeclaredFields();
    List<String> tableTitleList = Lists.newArrayList();
    for (Field field : declaredFields) {
        if (null != field.getAnnotation(ExcelProperty.class)) {
            String[] value = field.getAnnotation(ExcelProperty.class).value();
            if (value.length > 0) {
                tableTitleList.add(value[0]);
            }
        }
    }

    if (importTitleList.size() != tableTitleList.size()) {
        throw new ServiceException("导入模板错误");
    } else {
        for (int i = 0; i < importTitleList.size(); i++) {
            String s = importTitleList.get(i).toString().trim();
            if (!s.equals(tableTitleList.get(i).trim())) {
                throw new ServiceException("导入模板错误");
            }
        }
    }
}

导出列表设置列的下拉选项

public static void exportExcelWithSelect(HttpServletResponse response, String fileName, String sheetName,
                                              List<?> list, Class<?> pojoClass,  Map<Integer, List<String>> selectMap) {
    if (StringUtils.isBlank(fileName)) {
        //当前日期
        fileName = DateUtils.format(new Date());
    }
    try {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");

        WriteCellStyle writeCellStyle = new WriteCellStyle();
        writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        writeCellStyle.setBorderTop(BorderStyle.THIN);
        writeCellStyle.setBorderLeft(BorderStyle.THIN);
        writeCellStyle.setBorderRight(BorderStyle.THIN);
        writeCellStyle.setBorderBottom(BorderStyle.THIN);

        EasyExcel.write(response.getOutputStream(), pojoClass).sheet(sheetName)
                .registerWriteHandler(new HorizontalCellStyleStrategy(null, writeCellStyle))
                .registerWriteHandler(new CustomSheetWriteHandler(selectMap))
                .doWrite(list);
    } catch (Exception e) {
        throw new ServiceException("导出文件失败:", e.getMessage());
    }

}

public class CustomSheetWriteHandler implements SheetWriteHandler {

    /**
     * 构造下拉选项单元格列的位置,以及下拉选项可选参数值的map集合
     * key:下拉选项要放到哪个单元格,比如A列的单元格那就是0,C列的单元格,那就是2
     * value:key对应的那个单元格下拉列表里的数据项,比如这里就是下拉选项1..100
     */
    private final Map<Integer, List<String>> selectMap;

    public CustomSheetWriteHandler(Map<Integer, List<String>> selectMap) {
        this.selectMap = selectMap;
    }

    /**
     * 想实现Excel引用其他sheet页数据作为单元格下拉选项值,
     * 需要重写该方法
     */
    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        // 获取第一个sheet页
        Sheet sheet = writeSheetHolder.getCachedSheet();
        // 获取sheet页的数据校验对象
        DataValidationHelper helper = sheet.getDataValidationHelper();
        // 获取工作簿对象,用于创建存放下拉数据的字典sheet数据页
        Workbook workbook = writeWorkbookHolder.getWorkbook();

        // 迭代索引,用于存放下拉数据的字典sheet数据页命名
        int index = 1;
        for (Map.Entry<Integer, List<String>> entry : selectMap.entrySet()) {

            // 设置存放下拉数据的字典sheet,并把这些sheet隐藏掉,这样用户交互更友好
            String dictSheetName = "dict_hide_sheet" + index;
            Sheet dictSheet = workbook.createSheet(dictSheetName);
            // 隐藏字典sheet页
            workbook.setSheetHidden(index++, true);

            // 设置下拉列表覆盖的行数,从第一行开始到最后一行,这里注意,Excel行的
            // 索引是从0开始的,我这边第0行是标题行,第1行开始时数据化,可根据实
            // 际业务设置真正的数据开始行,如果要设置到最后一行,那么一定注意,
            // 最后一行的行索引是1048575,千万别写成1048576,不然会导致下拉列表
            // 失效,出不来
            CellRangeAddressList infoList = new CellRangeAddressList(1, 1048575, entry.getKey(), entry.getKey());
            int rowLen = entry.getValue().size();
            for (int i = 0; i < rowLen; i++) {
                // 向字典sheet写数据,从第一行开始写,此处可根据自己业务需要,自定
                // 义从第几行还是写,写的时候注意一下行索引是从0开始的即可
                dictSheet.createRow(i).createCell(0).setCellValue(entry.getValue().get(i));
            }

            // 设置关联数据公式,这个格式跟Excel设置有效性数据的表达式是一样的
            String refers = dictSheetName + "!$A$1:$A$" + entry.getValue().size();
            Name name = workbook.createName();
            name.setNameName(dictSheetName);
            // 将关联公式和sheet页做关联
            name.setRefersToFormula(refers);

            // 将上面设置好的下拉列表字典sheet页和目标sheet关联起来
            DataValidationConstraint constraint = helper.createFormulaListConstraint(dictSheetName);
            DataValidation dataValidation = helper.createValidation(constraint, infoList);
            sheet.addValidationData(dataValidation);
        }
    }
}

导出时指定列 合并单元格


public static void exportExcelMerge(HttpServletResponse response, String fileName, String sheetName, List<?> list, Class<?> pojoClass, List<Integer> mergeColumnList) {
    if (StringUtils.isBlank(fileName)) {
        //当前日期
        fileName = DateUtils.format(new Date());
    }
    try {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");

        WriteCellStyle writeCellStyle = new WriteCellStyle();
        writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        writeCellStyle.setBorderTop(BorderStyle.THIN);
        writeCellStyle.setBorderLeft(BorderStyle.THIN);
        writeCellStyle.setBorderRight(BorderStyle.THIN);
        writeCellStyle.setBorderBottom(BorderStyle.THIN);

        EasyExcel.write(response.getOutputStream(), pojoClass).sheet(sheetName).registerWriteHandler(new CellWriteHandler() {
                    @Override
                    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
                        //判断当前为表头,不执行操作
                        if (isHead) {
                            return;
                        }

                        if (!mergeColumnList.contains(cell.getColumnIndex())) {
                            return;
                        }

                        Sheet sheet = cell.getSheet();
                        int rowIndexCurr = cell.getRowIndex();
                        int rowIndexPrev = rowIndexCurr - 1;
                        Row rowCurr = cell.getRow();
                        Row rowPrev = sheet.getRow(rowIndexPrev);
                        Cell cellPrev = rowPrev.getCell(cell.getColumnIndex());

                        Object cellValueCurr = "";
                        Object cellValuePrev = "";
                        if (!CellType.BLANK.equals(cell.getCellTypeEnum())) {
                            cellValueCurr = cell.getCellTypeEnum().equals(CellType.STRING) ? cell.getStringCellValue() : cell.getNumericCellValue();
                        }
                        if (!CellType.BLANK.equals(cell.getCellTypeEnum())) {
                            cellValuePrev = cell.getCellTypeEnum().equals(CellType.STRING) ? cellPrev.getStringCellValue() : cellPrev.getNumericCellValue();
                        }

                        //判断当前单元格与上面单元格是否相等,不相等不执行操作
                        if (!cellValueCurr.equals(cellValuePrev)) {
                            return;
                        }

                        if (!rowPrev.getCell(0).getStringCellValue().equals(rowCurr.getCell(0).getStringCellValue())) {
                            return;
                        }

                        //从 Sheet 中,获取所有合并区域
                        List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
                        //是否合并过
                        boolean merged = false;
                        //遍历合并区域集合
                        for (int i = 0; i < mergedRegions.size(); i++) {
                            CellRangeAddress cellAddresses = mergedRegions.get(i);
                            if (cellAddresses.isInRange(rowIndexPrev, cell.getColumnIndex())) {
                                sheet.removeMergedRegion(i);
                                cellAddresses.setLastRow(rowIndexCurr);
                                sheet.addMergedRegion(cellAddresses);
                                //已完成合并
                                merged = true;
                                break;
                            }
                        }

                        if (!merged) {
                            CellRangeAddress cellAddresses = new CellRangeAddress(rowIndexPrev, rowIndexCurr, cell.getColumnIndex(), cell.getColumnIndex());
                            sheet.addMergedRegion(cellAddresses);
                        }
                    }
                })
                .registerWriteHandler(new HorizontalCellStyleStrategy(null, writeCellStyle))
                .doWrite(list);
    } catch (Exception e) {
        throw new ServiceException("导出文件失败:", e.getMessage());
    }

}

以前用POI写Excel,感觉自己像在'用算盘弹钢琴';现在用EasyExcel,终于能优雅地'用钢琴弹《野蜂飞舞》'了。记住:代码可以卷,但内存不能炸!

EasyExcel就像Excel界的'独孤九剑',以无招胜有招,用流式解析破内存之局,用注解映射化格式之乱。从此,Excel江湖再无'内存爆炸'的传说,只有'优雅处理'的传说。各位少侠,下次见!