• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

优化导入大批量数据的Excel上万行的导入SpringBoot + Apache POI

武飞扬头像
ydf84
帮助1

问题描述

操作员在导入Excel文件时发生了OOM(文件的数据3w行 * 60列),发生了OOM,jvm的运行内存1G

领导要求必须解决这个问题。

正在运行的代码

// 仅2007版本的解析
 Workbook workbook = new XSSFWorkbook(inputStream);
 Sheet sheet = workbook.getSheetAt(0);
 for (int rowIndex = 1; rowIndex < rowCount; rowIndex  ) {
    Row row = sheet.getRow(rowIndex);
    if (Objects.nonNull(row)) {
        int cellCount = row.getPhysicalNumberOfCells();
        for (int i = 0; i < cellCount; i  ) {
            Cell cell = getCell(row, i);
        	String value = cell.getStringCellValue();
        	// ..... 读取的值处理
        }
    }
}

看着代码好像没有啥问题,百度下大家都是这么写的。。。。

解决问题

研究下可以怎么优化

Excel大批量导入导出解决方案

通过这篇文章详细了解了POI对导入分为3种模式,用户模式User Model,事件模式Event Model,还有Event User Model。
了解了导入的原理后,就知道怎么优化这个问题了,因为我们只考虑xlsx格式的文件导入,所以定位优化点改为POI的Event User Model解析。

如果你不想看官方的例子,就直接看我的改造代码好了

优化后的代码

Maven依赖不用修改

读取Excel文件流并解析文件

 try (final OPCPackage pck = OPCPackage.open(inputStream)) {
    final XSSFReader reader = new XSSFReader(pck);
    final StylesTable stylesTable = reader.getStylesTable();
    final Iterator<InputStream> sheets = reader.getSheetsData();

    final ReadOnlySharedStringsTable rsst = new ReadOnlySharedStringsTable(pck);
    // 存储解析的所有行对象
    List<T> result = new LinkedList<>();
    
	// 重要的是这个文件的实现SheetHandler
    final SheetHandler<T> contentHandler = new SheetHandler<T>(rowClass, result, getFirstDataRowIndex());

    final XSSFSheetXMLHandler handler = new XSSFSheetXMLHandler(stylesTable, rsst, contentHandler, new MyDataFormatter(), false);

    while (sheets.hasNext()) {
        final InputStream sheet = sheets.next();
        final InputSource sheetSource = new InputSource(sheet);
        final XMLReader xmlReader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
        xmlReader.setContentHandler(handler);
        xmlReader.parse(sheetSource);
        sheet.close();
    }
    // 后面可以进行校验数据 ObjectValidationHolder.verify(),后面有时间再多放些后续的处理代码逻辑
    return result;
}

SheetHandler.java 实现 重点关注 带注解@Override 的方法

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.usermodel.XSSFComment;
import org.df.excel.constants.EnumHelper;
import org.df.excel.constants.IEnum;
import org.df.excel.dto.BaseImportDTO;
import org.df.excel.upload.excel.annoation.Cell;
import org.df.excel.util.bean.ReflectUtil;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 读取一个sheet的处理器
 * 循环遍历读取每一行数据
 *  
 * @author ff
 * @date 2022/8/30 14:29
 */
@Slf4j
// 如果你不需要记录行索引,T 可以不继承任何基类
public class SheetHandler<T extends BaseImportDTO> implements XSSFSheetXMLHandler.SheetContentsHandler {
    /**
     * 转换后的每行数据JavaBean实例
     */
    private T rowBean;
    /**
     * 行数据的JavaBean类型
     */
    private final Class<T> rowClass;
    /**
     * 读取sheet中所有的数据,存储的list
     */
    private final List<T> dataList;
    /**
     * 第一行数据的行索引。从0开始
     */
    private final int firstDataRowIndex;
    /**
     * 行数据映射的Java类中所有的字段的集合Map
     * Key: 列头A,B,C....
     * Value: 对应Java类中的字段名
     */
    private final Map<String, Field> rowBeanFieldMap;

    public SheetHandler(Class<T> rowClass, List<T> dataList, int firstDataRowIndex) {
        this.rowClass = rowClass;
        this.dataList = dataList;
        this.firstDataRowIndex = firstDataRowIndex;
        this.rowBeanFieldMap = ReflectUtil.getFieldMap(rowClass);
    }


    /**
     * 开始读一行的数据
     *
     * @param rowIndex
     */
    @Override
    public void startRow(int rowIndex) {
        if (rowIndex >= firstDataRowIndex) {
            try {
                this.rowBean = this.rowClass.newInstance();
                // 如果你不需要记录行索引,可以不继承任何基类
                rowBean.setRowIndex(rowIndex);
            } catch (ReflectiveOperationException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 行数据读取结束
     *
     * @param rowIndex
     */
    @Override
    public void endRow(int rowIndex) {
        if (Objects.nonNull(this.rowBean)) {
            this.dataList.add(this.rowBean);
        }
        this.rowBean = null;
    }

    /**
     * 读一个单元格的值
     *
     * @param cellAddress 单元格地址(A1,V3,C3....)
     * @param cellValue   单元格的值
     * @param xssfComment
     */
    @Override
    public void cell(String cellAddress, String cellValue, XSSFComment xssfComment) {
        if (Objects.isNull(rowBean) || StringUtils.isBlank(cellValue)) {
            return;
        }
        Field field = getField(cellAddress, this.rowBeanFieldMap);
        try {
            if (Objects.nonNull(field)) {
                field.setAccessible(true);
                Object fieldValue = getCellValue(cellValue, field);
                ReflectionUtils.setField(field, rowBean, fieldValue);
            }
        } catch (Exception e) {
            log.error("字段[{}]映射的单元格[{}]取值异常", field.getName(), cellAddress, e);

        }

    }

    private Object getCellValue(String cellValue, Field field) {
        if (StringUtils.isBlank(cellValue)) {
            return null;
        }
        String value = convertCellValue(cellValue, field);
        if (String.class.isAssignableFrom(field.getType())) {
            return value;
        }
        if (Integer.class.isAssignableFrom(field.getType())) {
            return Integer.parseInt(value);
        }
        if (Long.class.isAssignableFrom(field.getType())) {
            return Long.parseLong(value);
        }
        return cellValue;
    }

    private String convertCellValue(String cellValue, Field field) {
        final Cell annotation = field.getAnnotation(Cell.class);
        if (Objects.nonNull(annotation)) {
            final boolean need = annotation.needConvert();
            if (need) {
                final Class<? extends IEnum> convert = annotation.convert();
                return EnumHelper.getCodeByText(convert, cellValue);
            }
        }
        return cellValue;
    }

    private Field getField(String cellAddress, Map<String, Field> rowBeanFieldMap) {
        String columnCode = getColumnCode(cellAddress);
        return rowBeanFieldMap.get(columnCode);
    }

    /**
     * 获取单元格的列头代码
     *
     * @param cellAddress A1,B1,C1...
     * @return
     */
    private String getColumnCode(String cellAddress) {
        return cellAddress.replaceAll("\\d $", "");
    }
}

MyDataFormatter.java

import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil;

import java.text.SimpleDateFormat;

/**
 * @author ff
 * @date 2022/8/30 17:48
 */
public class MyDataFormatter extends DataFormatter {
    @Override
    public String formatRawCellContents(double value, int formatIndex, String formatString, boolean use1904Windowing) {
        if (DateUtil.isADateFormat(formatIndex, formatString)) {
            if (DateUtil.isValidExcelDate(value)) {
                return new SimpleDateFormat("yyyy-MM-dd").format(value);
            }
        }
        return super.formatRawCellContents(value, formatIndex, formatString);
    }
}

测试结果就不放了,效率刚刚的,完美解决OOM~

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhghafbc
系列文章
更多 icon
同类精品
更多 icon
继续加载