乐趣区

关于java:我所知道报表之POI百万数据导入测试分析原理解决总结

一、传统形式上进行百万导入


依据后面咱们的形式解决百万导出,那么就要做百万次循环

// 获取数据
private static Object getValue(Cell cell) {
    Object value = null;
    switch (cell.getCellType()) {
        case STRING: // 字符串类型
            value = cell.getStringCellValue();
            break;
        case BOOLEAN: //boolean 类型
            value = cell.getBooleanCellValue();
            break;
        case NUMERIC: // 数字类型(蕴含日期和一般数字)if(DateUtil.isCellDateFormatted(cell)) {value = cell.getDateCellValue();
            }else{value = cell.getNumericCellValue();
            }
            break;
        case FORMULA: // 公式类型
            value = cell.getCellFormula();
            break;
        default:
            break;
    }
    return value;
}
//1. 依据上传流信息创立工作簿
Workbook workbook = WorkbookFactory.create(attachment.getInputStream());
//2. 获取第一个 sheet
Sheet sheet = workbook.getSheetAt(0);
List<User> users = new ArrayList<>();
//3. 从第二行开始获取数据
for (int rowNum = 1; rowNum <sheet.getLastRowNum(); rowNum++) {
    // 获取行对象
    Row row = sheet.getRow(rowNum);
    // 获取该行的所有列单元格数量
    Object objs[] = new Object[row.getLastCellNum()];
    // 从第二列获取数据
    for(int cellNum = 0; cellNum < row.getLastCellNum();cellNum++) {Cell cell = row.getCell(cellNum);
        objs[cellNum] = getValue(cell);
    }
    // 依据每一列结构用户对象
    User user = new User(objs);
    users.add(user);
}
// 第一个参数:用户列表,第二个参数:部门编码
userService.save(users);
return Result.SUCCESS();

这种形式去执行百万数据的读取,那么咱们在 Jvisualvm 看看效率

观看如图所示,咱们发现会一直的占用内存,直到吃满跑出 oom 异样

那么为什么会这样呢?

二、传统形式上问题剖析


那么为什么会占用那么多内存呢?

加载并读取 Excel 时,是通过一次性的将所有数据加载到内存中再去解析每个单元格内容。

当 Excel 数据量较大时,因为不同的运行环境可能会造成内存不足甚至 OOM 异样

在 ApachePoi 官网提供了对操作大数据量的导入导出的工具和解决办法,操作 Excel2007 应用 XSSF 对象,能够分为三种模式:

  • 用户模式:用户模式有许多封装好的办法操作简略,但 创立太多的对象,十分耗内存(之前应用的办法)
  • 事件模式:基于 SAX 形式解析 XML,SAX 全称 Simple API for XML,它是一个接口,也是一个软件包。它是 一种 XML 解析的代替办法,不同于 DOM 解析 XML 文档时把所有内容一次性加载到内存中的形式,它逐行扫描文档,一边扫描,一边解析
  • SXSSF 对象:是用来生成海量 excel 数据文件,次要原理是借助长期存储空间生成 excel

Apache POI 官网提供有一张图片,形容了基于用户模式,事件模式,以及应用 SXSSF 三种形式操作 Excel 的个性以及 CUP 和内存占用状况

三、解决思路剖析


咱们刚刚进行问题剖析晓得当咱们加载并读取 Excel 时,是通过一次性的将所有数据加载到内存中再去解析每个单元格内容

当百万数据级别的 Excel 导入时,随着单元格的一直读取,内存中对象越来越多,直至内存溢出。

下面提到 POI 提供刚给 excel 大数据时读取有两种模式:
1. 用户模式:应用系列封装好的 API 操作 Excel,使得操作简略但占用内存
2. 事件驱动:基于 sax 的读取形式,逐行扫描文档,一边扫描,一边解析

那么为什么应用事件驱动就能够解决这个问题呢?

事件驱动原理解析

事件驱动一般来说会有一个事件监听的主线程,而所有要解决的事件则须要注册意思指产生某件事情的时候,委托给事件处理器进行解决

比如说:有一个事件处理器当每个月发工资时准时贷款一半工资

咱们刚刚提到过 SAX 是基于事件驱动的一种 xml 解析计划

那么事件处理器在解析 excel 的时候是如何去应用的呢?

其实就是指定 xml 里的一些节点,在指定的节点解析后触发事件

依照这样的思路办法:

咱们在解析 excel 时每一行实现后进行触发事件,事件做的事件就是 将解析的以后行的内存进行销毁

这样咱们进行百万数据处理的时候,则解决完一行就开释一行

然而也有毛病:因为咱们是解决完一行就开释一行,则用完就销毁了不可在用

四、应用事件驱动来优化传统形式


那么依据 SAX 事件驱动进行读取数据次要分几种步骤

1. 设置 POI 的事件模式,指定应用事件驱动去解析 EXCEL 来做

  • 依据 Excel 获取文件流
  • 依据文件流创立 OPCPackage
  • 创立 XSSFReader 对象

2. 应用 Sax 进行解析

  • 自定义 Sheet 处理器
  • 创立 Sax 的 XMlReader 对象
  • 设置 Sheet 的事件处理器
  • 逐行读取

接下来咱们依据步骤思路,去实现百万数据的读取,首先咱们将 excel 对应的数据实体类创立

public class PoiEntity {
    
    private String id;
    private String name;
    private String tel;
    
    // 省略 get、set 办法
}

接着创立咱们的自定义 Sheet 基于 Sax 的解析处理器 SheetHandler

/**
 *    自定义 Sheet 基于 Sax 的解析处理器
 *    解决每一行数据读取
 *    实现接口 org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler
 */
public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {

    // 封装实体对象
    private PoiEntity entity;

    /**
     * 解析行开始
     */
    @Override
    public void startRow(int rowNum) {if (rowNum >0) {entity = new PoiEntity();
       }
   }
    
    /**
     * 解析每一个单元格
     * cellReference       单元格名称
     * formattedValue      单元格数据值
     * comment             单元格批注
     */
     @Override
    public void cell(String cellReference, String formattedValue, XSSFComment comment){if(entity != null) {
            // 因为单元格名称比拟长,所以截取首字母
            switch (cellReference.substring(0, 1)) {
                case "A":
                    entity.setId(formattedValue);
                    break;
                case "B":
                    entity.setName(formattedValue);
                    break;
                case "C":
                    entity.setTel(formattedValue);
                    break;
                default:
                    break;
           }
       }
    }

    /**
     * 解析每一行完结时触发
     */
    public void endRow(int rowNum) {System.out.println(entity);
        // 个别进行应用对象进行业务解决.....
   }
}   

接下来咱们应用事件模型来解析百万数据 excel 报表

//excel 文档门路
String path = "e:\\demo.xlsx";

//============ 设置 POI 的事件模式,指定应用事件驱动去解析 EXCEL 来做 ============
//1. 依据 Excel 获取 OPCPackage 对象
OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ);

//2. 创立 XSSFReader 对象
XSSFReader reader = new XSSFReader(pkg);

//3. 获取 String 类型表格 SharedStringsTable 对象
SharedStringsTable sst = reader.getSharedStringsTable();

//4. 获取款式表格 StylesTable 对象
StylesTable styles = reader.getStylesTable();

//============ 应用 Sax 进行解析 ============
//5. 创立 Sax 的 XmlReader 对象
XMLReader parser = XMLReaderFactory.createXMLReader();

//6. 设置 Sheet 的事件处理器
parser.setContentHandler(new XSSFSheetXMLHandler(styles,sst,new SheetHandler(), false));

//7. 逐行读取(因为有多个 sheet 所以须要迭代读取)
XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator)reader.getSheetsData();
while (sheets.hasNext()) {InputStream sheetstream = sheets.next();// 每一个 sheet 的数据流
    InputSource sheetSource = new InputSource(sheetstream);
    try {parser.parse(sheetSource);
   } finally {sheetstream.close();
   }
}

让咱们应用 Jvisualvm 看看事件驱动的形式是什么效率吧

通过简略的剖析以及运行两种模式进行比拟,能够看到用户模式下应用更简略的代码实现了 Excel 读取。

然而在读取大文件时 CPU 和内存都不现实;而事件模式尽管代码写起来比拟繁琐,然而在读取大文件时 CPU 和内存更加占优。

退出移动版