[alibaba/easyexcel]最新的 3.2.1 版本读取 xls 文件时,如果数据是整数会读取出小数

2023-03-08 892 views
4
触发Bug的代码
     EasyExcelFactory.read(excelStream, new AnalysisEventListener<LinkedHashMap<Integer, ReadCellData<?>>>() {

     }).useDefaultListener(false).readDefaultReturn(ReadDefaultReturnEnum.ACTUAL_DATA).sheet().doRead();
提示的异常或者没有达到的效果

例如,值是 1,会读取为 1.0,值是 2,会读取为 2.0,如何才能读取到原始的数据?

回答

6

用实体类,不用Map就行了

4

我们的需求是适配不同客户的 xls 数据,读取数据的时候,无法知道客户的的 xls 的“实体类”定义,只能 Map 去读取,这有解决方案么?

7

数字默认是double。 我有一个方案,你invoke拿到的hashmap,你判断如果是double并且是整数,你把他put覆盖掉

0

但是这样会增加额外开销,你可以在第一次遍历的之后缓存对应的key

2

这样做还是有问题,按照你的方案:

  • 如果原始数据是 1.0,我们拿到是 1
  • 如果原始数据是 1.00,我们拿到的是 1
  • 如果原始数据是 1,我们拿到是 1

但我们的需求是拿到真正的客户的原始数据,不管是 1.0 、1.00 还是 1

2

POI 的方案是提供了 DataFormatter 可以获取到 cell 的原始数据,例如:

// 给一个 xls 文件流
Workbook workbook = WorkbookFactory.create(excelStream); 

// 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0);

// 创建数据格式化对象
DataFormatter dataFormatter = new DataFormatter();

// 遍历所有行和列,打印单元格值
for (Row row : sheet) {
    for (Cell cell : row) {
        String cellValue = dataFormatter.formatCellValue(cell); // 这样获取的 String 是真正的原始数据
        System.out.print(cellValue + "\t");
    }
    System.out.println();
}
6

你拿到数据之后的目的是什么,你可以根据接下来的业务进行操作

0

我们的目的是同步数据为 CSV,用于 AI 训练,但就目前读取到数据就是错误的,因为 1.0 不等于 1,对于 AI 任务来说

8
SHOW ME THE CODE
    private String resolve(ReadCellData<Object> cellData) {
        CellDataTypeEnum dataTypeEnum = cellData.getType();
        if (Objects.equals(dataTypeEnum, CellDataTypeEnum.NUMBER)) {
            BigDecimal bigDecimal = cellData.getOriginalNumberValue();
            if (bigDecimal.scale() > 1) {
                return bigDecimal.toString();
            }
            return new DataFormatter().formatRawCellContents(
                    cellData.getOriginalNumberValue().doubleValue(),
                    cellData.getDataFormatData().getIndex(),
                    cellData.getDataFormatData().getFormat());
        }
        return cellData.getStringValue();
    }
分析

需要注意的是对于 xls 这种精度错误问题,在 xlsx 并没有复现

我们现在的方案是, 读取出 cell 的数据为 ReadCellData<Object>,看其是否是 CellDataTypeEnum.NUMBER,通过断点发现,由于 easy excel 会将其读取 BigDecimal,那么 decimal 值可以通过 cellData.getOriginalNumberValue(); 获取到

由于 easy excel 底层读取依赖于 POI,结合 issue 我之前写的 POI DataFormatter 的解决方案以及断点 easy excel 的 ReadCellData 提供的 cell 元信息,想要拿取 cell 真正的原始数据,可以有如下解决方案:

  • 如果 decimal 的 scale() 大于 1 ,那么直接返回 toString();
  • 否则,new org.apache.poi.ss.usermodel.DataFormatter()#formatRawCellContents(double value, int formatIndex, String formatString) 转换获取到原始值
1

感谢