现在需要将一个导出列表数据到Excel表格的功能进行改造,将指定列相同数据自动合并单元格。
如上图所示,指定A、B两列自动合并,如图所示(6、7),(8、9),(13、14、15)要自动合并单元格。
EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。
EasyExcel相比其他Excel解析框架(Apache poi和jxl),拥有更好的内存消耗管理算法。特别是对07版Excel的解决,EasyExcel重写了底层解析逻辑,一个3M的Excel解析只需要几M内存,但是用poi解析可能需要100M左右的内存。EasyExcel提高了读取性能,64M内存20秒读取75M的Excel,还有更快的极速模式,但是消耗的内存会更多一些。
EasyExcel支持自定义策略合并单元格,可以方便快捷填充数据到模板中,有活跃的中文社区支持,完善的测试用例可以覆盖大部分业务场景的使用。
使用EasyExcel导出Excel代码示例:
@Test public void testWrite() throws IOException { List<DemoMergeData> resultList = new ArrayList<>(); resultList.add(DemoMergeData.builder().id(1).sub("张胜男").date("12").build()); resultList.add(DemoMergeData.builder().id(1).sub("李四").date("224").build()); resultList.add(DemoMergeData.builder().id(3).sub("王五").date("224").build()); resultList.add(DemoMergeData.builder().id(4).sub("赵柳").date("224").build()); resultList.add(DemoMergeData.builder().id(5).sub("赵柳").date("224").build()); resultList.add(DemoMergeData.builder().id(5).sub("赵柳").date("224").build()); resultList.add(DemoMergeData.builder().id(8).sub("赵柳").date("224").build()); resultList.add(DemoMergeData.builder().id(8).sub("赵柳").date("224").build()); resultList.add(DemoMergeData.builder().id(9).sub("陈琪").date("224").build()); resultList.add(DemoMergeData.builder().id(10).sub("小白").date("241").build()); resultList.add(DemoMergeData.builder().id(11).sub("小黑").date("241").build()); resultList.add(DemoMergeData.builder().id(12).sub("小黑").date("241").build()); resultList.add(DemoMergeData.builder().id(12).sub("小黑").date("241").build()); resultList.add(DemoMergeData.builder().id(12).sub("小黑").date("241").build()); resultList.add(DemoMergeData.builder().id(13).sub("小黑").date("241").build()); // 设置文件名称 String fileName = "C:\\Users\\Administrator\\Downloads\\test\\t1.xlsx"; File file = new File(fileName); if (!file.exists()) { file.createNewFile(); } // sheet名称 EasyExcel.write(fileName, DemoMergeData.class) .autoCloseStream(Boolean.TRUE) .sheet("测试导出").doWrite(resultList); }
导出表格样式:
自定义策略一代码示例:
public class ExcelFillCellMergeStrategy implements CellWriteHandler { private int[] mergeColumnIndex; private int mergeRowIndex; public ExcelFillCellMergeStrategy() { } public ExcelFillCellMergeStrategy(int mergeRowIndex, int[] mergeColumnIndex) { this.mergeRowIndex = mergeRowIndex; this.mergeColumnIndex = mergeColumnIndex; } @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) { //当前行 int curRowIndex = cell.getRowIndex(); //当前列 int curColIndex = cell.getColumnIndex(); if (curRowIndex > mergeRowIndex) { for (int columnIndex : mergeColumnIndex) { if (curColIndex == columnIndex) { mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex); break; } } } } private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) { //获取当前行的当前列的数据和上一行的当前列列数据,通过上一行数据是否相同进行合并 Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue(); Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex); Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue(); // 比较当前行的第一列的单元格与上一行是否相同,相同合并当前单元格与上一行 if (curData.equals(preData)) { Sheet sheet = writeSheetHolder.getSheet(); List<CellRangeAddress> mergeRegions = sheet.getMergedRegions(); boolean isMerged = false; for (int i = 0; i < mergeRegions.size() && !isMerged; i++) { CellRangeAddress cellRangeAddr = mergeRegions.get(i); // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元 if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) { sheet.removeMergedRegion(i); cellRangeAddr.setLastRow(curRowIndex); sheet.addMergedRegion(cellRangeAddr); isMerged = true; } } // 若上一个单元格未被合并,则新增合并单元 if (!isMerged) { CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex); sheet.addMergedRegion(cellRangeAddress); } } } @Override public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer integer, Integer integer1, Boolean aBoolean) { } @Override public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer integer, Boolean aBoolean) { }}
测试代码示例:
@Test public void testWrite() throws IOException { int[] mergeColumnIndex = {0,1}; // 需要从第几行开始合并 int mergeRowIndex = 1; // 数据就不初始化了 List<DemoMergeData> resultList = new ArrayList<>(); // 设置文件名称 String fileName = "C:\\Users\\Administrator\\Downloads\\test\\t1.xlsx"; File file = new File(fileName); if (!file.exists()) { file.createNewFile(); } // sheet名称 EasyExcel.write(fileName, DemoMergeData.class) .autoCloseStream(Boolean.TRUE) .registerWriteHandler(new ExcelFillCellMergeStrategy(mergeRowIndex,mergeColumnIndex)) .sheet("测试导出").doWrite(resultList); }
导出样式:
自定义策略二代码示例:
public class MultiColumnMergeStrategy extends AbstractMergeStrategy { // 合并的列编号,从0开始,指定的index或自己按字段顺序数 private Integer startCellIndex = 0; private Integer endCellIndex = 0; // 数据集大小,用于区别结束行位置 private Integer maxRow = 0; // 禁止无参声明 private MultiColumnMergeStrategy() { } public MultiColumnMergeStrategy(Integer maxRow, Integer startCellIndex, Integer endCellIndex) { this.startCellIndex = startCellIndex; this.endCellIndex = endCellIndex; this.maxRow = maxRow; } // 记录上一次合并的信息 private final List<List<String>> dataList = new ArrayList<>(); @Override protected void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex) { int currentCellIndex = cell.getColumnIndex(); int currentRowIndex = cell.getRowIndex(); // 判断该列是否需要合并 if (currentCellIndex < startCellIndex || currentCellIndex > endCellIndex) { return; } Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue(); String currentCellValue = curData.toString(); List<String> rowList; if (dataList.size() > currentRowIndex - 1) { rowList = dataList.get(currentRowIndex - 1); } else { rowList = new ArrayList<>(); dataList.add(rowList); } rowList.add(currentCellValue); // 结束的位置触发下最后一次没完成的合并 if (relativeRowIndex == (maxRow - 1) && currentCellIndex == endCellIndex) { System.out.println(JSONObject.toJSONString(dataList)); List<String> tempList = null; Integer tempIndex = null; for (int i = 0; i < dataList.size(); i++) { if (tempList == null) { tempList = dataList.get(i); tempIndex = i; continue; } List<String> currList = dataList.get(i); if (tempList.equals(currList)) { if (i >= dataList.size() - 1) { // 结束的位置触发下最后一次没完成的合并 for (int j = 0; j < tempList.size(); j++) {sheet.addMergedRegionUnsafe(new CellRangeAddress(tempIndex + 1, i + 1, startCellIndex + j, startCellIndex + j)); } } continue; } // 当前行数据和上一行数据不同且上面有多行相同数据时触发合并 if (i - tempIndex > 1) { for (int j = 0; j < tempList.size(); j++) { sheet.addMergedRegionUnsafe(new CellRangeAddress(tempIndex + 1, i, startCellIndex + j, startCellIndex + j)); } } tempIndex = i; tempList = currList; } } }}
测试代码示例:
@Test public void testWrite() throws IOException { // 数据就不初始化了 List<DemoMergeData> resultList = new ArrayList<>(); // 设置文件名称 String fileName = "C:\\Users\\Administrator\\Downloads\\test\\t1.xlsx"; File file = new File(fileName); if (!file.exists()) { file.createNewFile(); } // sheet名称 EasyExcel.write(fileName, DemoMergeData.class) .autoCloseStream(Boolean.TRUE) .registerWriteHandler(new MultiColumnMergeStrategy(resultList.size(),0,1)) .sheet("测试导出").doWrite(resultList); }
导出样式:
EasyExcel功能灵活强大,可以根据自身业务场景去自定义样式,也可以使用通过模板填充功能实现导出国际化语言等复杂功能。