[alibaba/easyexcel]建议增加导入时解析合并的单元格

2024-05-10 519 views
2

建议描述 我司有导入一对多数据的需求且量比较大,为了使业务人员使用更友好采用合并单元格方式来制定模板,数据整体结构为一个object对象中包含一个或多个list,list为x行和y到y+n列的数据,样例数据如下:

image

目前我们对于单行数据采用easyExcel解析,对于一对多数据使用poi原始进行解析,但由于表格量大和人员使用频繁常常出现内存问题不得以减缓处理速度。曾经尝试使用easyExcel的原始Map<Integer, ReadCellData<?>>来尝试解决多单元格合并问题,但由于原生不支持对list范型类型解析需要改动原代码破坏度较高。所以希望是否可以考虑一下对合并单元格的解析需求。

回答

8

底层 用流试读取,必须先读取内容再读取合并单元格信息,没法做到同时读取。除非把所有数据读取到内存。 可以参照:读取合并单元格信息,会把那些单元格合并的信息读取出来,但是和数据结合需要自己做。 想要先读取合并单元格信息的话,可以直接读取2遍,第一遍读取合并单元格信息,第二遍读取数据。

5

读2遍建议能提供工具接口出来,方便使用获取。 并且如果只是为了读取合并信息,是否可以把读取行执行handler简化,省一些对数据行的处理时间。

3

读2遍建议能提供工具接口出来,方便使用获取。 并且如果只是为了读取合并信息,是否可以把读取行执行handler简化,省一些对数据行的处理时间。 业务侧要求研发有时候没有办法去解决,一对多读取的合并的需求也确实存在,我基于目前版本进行了二开,目前还好,但是性能会比原生低了一些,因为我要把所有数据读入到内存或者其它介质里然后再合并

3

刚写完一个版本。看似能实现。读取2遍,读合并单元格信息的时候,如果能开放出来跳过row的解析 应该会好很多。简单代码贴一下。


List<CellExtra> mergeCells = readMergeCells(file) ;
excelReader = EasyExcel.read(file).useDefaultListener(false).build();
ExcelReaderSheetBuilder sheetBuilder = EasyExcel.readSheet(parseParams.sheetNo())
            .headRowNumber(parseParams.headRowNumber());

        if (!mergeCells.isEmpty()) {
            sheetBuilder.registerReadListener(new FillMergeListener(mergeCells));
        }
        sheetBuilder.registerReadListener(new ModelBuildEventListener());
        sheetBuilder.registerReadListener(this.dataListener);
     if (Objects.nonNull(parseParams.head())) {
            sheetBuilder.head(parseParams.head());
        }
        readSheet = sheetBuilder.build();
excelReader.read(readSheet);

private List<CellExtra> readMergeCells(File file) {
        ReadMergeCellsListener readMergeCellsListener = new ReadMergeCellsListener();
        EasyExcel.read(file, readMergeCellsListener).extraRead(CellExtraTypeEnum.MERGE).sheet(parseParams.sheetNo()).doRead();
        return readMergeCellsListener.mergeCells;
    }

private static class ReadMergeCellsListener extends AnalysisEventListener {

        private List<CellExtra> mergeCells = new ArrayList<>();

        @Override
        public void invoke(Object data, AnalysisContext context) {

        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {

        }

        /**
         * 读取额外信息:合并单元格
         */
        @Override
        public void extra(CellExtra extra, AnalysisContext context) {
            switch (extra.getType()) {
                case MERGE: {
                    log.debug(
                        "额外信息是合并单元格,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
                        extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
                        extra.getLastColumnIndex());
                    mergeCells.add(extra);
                    break;
                }
                default:
            }
        }
    }

private static class FillMergeListener extends AnalysisEventListener<Map<Integer, ReadCellData<?>>> {

        private final List<CellExtra> mergedRegions;
        private final Map<String, CellExtra> mergedRegionMap = new HashMap<>();
        private final Set<Integer> mergedRowSet = new HashSet<>();

        //上一行数据
        private Map<Integer, ReadCellData<?>> last;

        public FillMergeListener(List<CellExtra> mergedRegions) {
            this.mergedRegions = mergedRegions;

            mergedRegions.forEach(cellAddresses -> {
                int firstColumn = cellAddresses.getFirstColumnIndex();
                int lastColumn = cellAddresses.getLastColumnIndex();
                int firstRow = cellAddresses.getFirstRowIndex();
                int lastRow = cellAddresses.getLastRowIndex();

                for (int i = firstRow; i <= lastRow; i++) {
                    mergedRowSet.add(i);
                    for (int j = firstColumn; j <= lastColumn; j++) {
                        mergedRegionMap.put(getMergedCellKey(i, j), cellAddresses);
                    }
                }

            });
        }

        private String getMergedCellKey(int rowIdx, int column) {
            return rowIdx + "|" + column;
        }

        @Override
        public void invoke(Map<Integer, ReadCellData<?>> data, AnalysisContext context) {
            // 获取行号
            ReadRowHolder readRowHolder = context.readRowHolder();
            Integer rowIdx = readRowHolder.getRowIndex();
            if (mergedRowSet.contains(rowIdx)) {
                //尝试处理合并单元格子的数据
                if (data != null) {
                    for (Map.Entry<Integer, ReadCellData<?>> entry : data.entrySet()) {
                        String cellKey = getMergedCellKey(rowIdx, entry.getKey());
                        if (mergedRegionMap.containsKey(cellKey)) {
                            mergeRegionValue(data, mergedRegionMap.get(cellKey), rowIdx, entry.getKey());
                        }
                    }
                }
            }

            last = data;
        }

        private void mergeRegionValue(Map<Integer, ReadCellData<?>> dataRow,
                                      CellExtra cellAddresses,
                                      int rowIdx, int colIdx) {

            ReadCellData<?> value = dataRow.get(colIdx);

            int firstColumn = cellAddresses.getFirstColumnIndex();
            int lastColumn = cellAddresses.getLastColumnIndex();
            int firstRow = cellAddresses.getFirstRowIndex();
            int lastRow = cellAddresses.getLastRowIndex();

            if (firstRow == rowIdx) {
                if (firstColumn == colIdx) {
                    //合并单元格的第一个格子内容,不需要处理
                    return;
                } else {
                    //非合并单元格的第一个格子内容,左右合并,因为按顺序的,所以像左取值
                    value = dataRow.get(colIdx - 1);
                    dataRow.put(colIdx, value);
                }
            } else {
                //非同一行,上下合并,向上取值
                if (this.last != null) {
                    value = this.last.get(colIdx);
                    dataRow.put(colIdx, value);
                }
            }
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {

        }

    }