操作员在导入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();
// ..... 读取的值处理
}
}
}
看着代码好像没有啥问题,百度下大家都是这么写的。。。。
有问题还得靠百度或CSDN,找到很多的关于导入的优化,我认为讲的比较清楚的文章贴出来给大家参考!!!
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;
}
@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+$", "");
}
}
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~