Excel百万数据导入内存溢出(OOM)解决方案,以及HSSFworkbook,XSSFworkbook,SXSSFworkbook失败分析

当遇到Excel百万数据导入导致内存溢出问题,尝试了HSSF、XSSF和SXSSF都无法解决。文章介绍了如何通过StreamingReader进行文件分片读取,解决了内存溢出问题,同时提出了数据分片写入数据库以优化性能,并强调及时清空对象以协助垃圾回收。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.问题背景

Excel百万数据导入,每行50+列,出现内存溢出,尝试HSSFworkbook,XSSFworkbook,SXSSFworkbook均未解决。

查阅资料,采取StreamingReader的方式,问题解决。

二.HSSFworkbook,XSSFworkbook,SXSSFworkbook分析

序号

版本

限制

描述

1

HSSFworkbook

Excel2003及以前,扩展名为.xls

65536行,256列

因为限制数据行数较少,一般不会发生OOM

2

XSSFworkbook

Excel2007以后,扩展名为.xlsx

1048576行,16384列

数据限制显著提升,但也正因可读写的数据量变大可能导致OOM

3

SXSSFworkbook

Excel2007以后,扩展名为.xlsx

大型Excel

用硬盘换内存,保存最新的rows在内存里,之前的rows写入硬盘,被写入硬盘的rows不可访问,想要访问就要指定读进内存数据的大小,以保证所有数据都能读取,因此,此方法适合大型Excel导出,对于导入还是会发生OOM。

表格整理参考官网:

https://poi.apache.org/apidocs/dev/org/apache/poi/ss/SpreadsheetVersion.html

https://poi.apache.org/apidocs/dev/org/apache/poi/xssf/streaming/SXSSFWorkbook.html

三.解决方案

1.文件分片读取

如上述介绍,HSSFworkbook数据上限太低,不可行;XSSFworkbook会发生OOM,不可行;SXSSFworkbook读取数据做导入还是会发生OOM,不可行。

使用StreamingReader可以解决大文件读取造成的OOM,它可以根据配置将一个大文件分成多个临时文件,再读取一部分Row到内存中,全部读取完成后将临时文件删掉。但是它仅支持XLSX文件,需要java8及以上运行环境,因为,StreamingReader是基于POI实现的,而POI 4.0需要java8及以上。

具体实现:

(1)导包

        <dependency>
            <groupId>com.monitorjbl</groupId>
            <artifactId>xlsx-streamer</artifactId>
            <version>2.2.0</version>
        </dependency>

(2)读取文件

import com.monitorjbl.xlsx.StreamingReader;
InputStream is = new FileInputStream(new File("/path/to/workbook.xlsx"));
Workbook workbook = StreamingReader.builder()
        .rowCacheSize(100)    // 缓存到内存中的行数(默认是10)
        .bufferSize(4096)     // 读取资源时,缓存到内存的字节大小(默认是1024)
        .open(is);            // InputStream或者XLSX格式的File(必须)

(3)读取sheet、行和单元格

for (Sheet sheet : workbook){
    System.out.println(sheet.getSheetName());
    for (Row r : sheet) {
        for (Cell c : r) {
            System.out.println(c.getStringCellValue());
        }
    }
}

(4)读取指定sheet

Sheet sheet = workbook.getSheet("Sheet名称")

(5)关于关流

官方文档描述StreamingReader会自动关闭流,不需要手动关闭。但我在使用过程中发现,不关闭流删除临时文件时会报错,删除失败。手动关闭流问题解决。

官方文档及源码:https://github.com/monitorjbl/excel-streaming-reader

2.数据分片写入数据库

本例导入数据时采取的是insert values批量插入的方式,若一个Excel中数据量过大他,同样会产生性能问题,可采用数据分片的方式,每次写入一定数量的数据,分批写入。

代码片段:

    private void test() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(new File("/path/to/workbook.xlsx"));
            Workbook wb = StreamingReader.builder()
                    .rowCacheSize(100)  //缓存到内存中的行数,默认是10
                    .bufferSize(4096)  //读取资源时,缓存到内存的字节大小,默认是1024
                    .open(fis);  //打开资源,必须,可以是InputStream或者是File,注意:只能打开XLSX格式的文件
            for (Sheet sheet : wb) {
                // sheet.getLastRowNum()获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1
                int sumPageNum = (sheet.getLastRowNum() + 1) / 1000 + 1;       //计算分片数
                for (int i = 1; i <= sumPageNum; i++) {     //读取每片范围内的内容
                    try {
                        int minRowNum = 1000 * (i - 1) + 1;    //计算每片读取的行数范围
                        int maxRowNum = 1000 * i;
                        for (Row row : sheet) {
                            if (isRowEmpty(row)) {
                                continue;
                            }
                            Map<String, Object> columnValueMap = new HashMap<>();
                            if (row.getRowNum() >= minRowNum && row.getRowNum() <= maxRowNum) {//控制每片读取的行
                                /**
                                 *   此处读取每行、每个单元格的内容,组装要写入数据库的对象
                                 */
                                if (row.getRowNum() == maxRowNum) {//读取到每片最大行数限制,跳出,执行数据写入
                                    break;
                                }
                            }
                        }
                        /**
                         *   此处执行数据写入
                         */
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        /**
                         *   清空对象
                         *   commonTableEntity = null;
                         *   columnValueList.clear();
                         */
                    }
                }
            }
            wb.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(fis);
        }
    }
3.及时清空对象和集合,以便被GC回收

将对象置为null或调用集合的clear()方法,此处不再赘述。

<think>好的,我现在需要回答用户关于Java导入导出Excel常见问题及解决方法的问题。首先,我得回想一下自己之前学过的相关知识,或者查资料。记得Apache POI是常用的库,处理Excel文件时可能会遇到内存溢出、日期格式、样式问题、大数据量处理等。 用户提到了三个引用,引用1提到解决常见问题的方法,引用2提到Java实体对象和注解的使用,引用3提到导入功能的用途和大数据处理。我需要结合这些信息。 首先,内存溢出问题,当处理大文件时,POI的UserModel可能占用太多内存。解决方法是用SXSSFWorkbook,它采用流式处理,只保留部分数据在内存中。比如引用2中的注解可能用于映射字段,但处理大数据时需要注意内存。 日期格式处理,ExcelJava的日期转换容易出错,需要正确设置DataFormat,或者用CellDateUtil来处理。比如引用3中提到数据处理和分析,日期格式的正确解析很重要。 样式丢失的问题,导出时合并单元格或样式设置不当会导致问题。需要正确使用CellStyle,并复用样式对象以避免内存问题。引用1提到解决方案帮助处理文件,样式处理是其中一部分。 性能优化方面,除了用SXSSF,还要避免频繁创建对象,比如在循环中创建CellStyle。引用3提到大数据处理,所以性能优化是关键。 兼容性问题,不同版本的Excel(xls和xlsx)需要不同的Workbook类,比如HSSF和XSSF。用户需要根据文件类型选择正确的类,或者自动检测,如引用2中的注解可能涉及文件类型判断。 公式计算问题,读取时可能需要evaluate公式,确保获取的是计算后的值。引用3中的数据处理可能需要处理公式结果。 最后,异常处理,比如文件被占用或格式错误,需要捕获异常并处理。引用1提到解决常见问题,异常处理也是其中之一。 用户可能需要示例代码,比如如何用SXSSFWorkbook导出大数据,或者如何转换日期格式。此外,生成相关问题时要覆盖性能优化、注解使用、兼容性处理、公式处理等。 要确保回答结构清晰,分点列出问题和解决方法,每个部分引用对应的资料。同时检查LaTeX格式是否正确,比如数学公式用$包围,独立公式用$$,但这里可能用不上太多数学公式,不过需要注意用户提到的系统级指令中的格式要求。 总结下来,回答应包括六个主要问题:内存溢出、日期格式、样式丢失、性能优化、兼容性、公式计算,每个问题对应解决方法,并引用提供的资料。最后生成相关问题,如大数据处理、注解应用、兼容性问题解决、样式设置技巧等。</think>### Java 导入导出 Excel 常见问题及解决方法 #### 1. **内存溢出OOM)问题** - **问题描述**:使用 `XSSFWorkbook` 处理大文件时,内存占用过高导致程序崩溃[^2]。 - **解决方法**: - 改用流式处理类 `SXSSFWorkbook`,仅保留部分行数据在内存中: ```java SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 缓存100行数据 ``` - 处理完成后调用 `dispose()` 清理临时文件[^1]。 #### 2. **日期格式解析错误** - **问题描述**:Excel中的日期格式(如 `2023-10-01`)读取为数值或字符串。 - **解决方法**: - 使用 `CellDateUtil` 判断日期类型: ```java if (DateUtil.isCellDateFormatted(cell)) { Date date = cell.getDateCellValue(); } ``` - 自定义日期转换工具类,结合 `SimpleDateFormat` 处理非标准格式[^3]。 #### 3. **样式丢失或混乱** - **问题描述**:导出时单元格合并、字体颜色等样式未生效。 - **解决方法**: - 复用 `CellStyle` 对象,避免频繁创建: ```java CellStyle style = workbook.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); ``` - 合并单元格使用 `sheet.addMergedRegion(new CellRangeAddress(startRow, endRow, startCol, endCol))`。 #### 4. **大数据量导出性能低下** - **问题描述**:导出数万行数据时耗时过长。 - **解决方法**: - 结合 `SXSSFWorkbook` 分页写入,每页处理固定行数(如 1000 行)。 - 关闭自动计算公式:`workbook.setForceFormulaRecalculation(false)`[^3]。 #### 5. **兼容性问题(xls vs xlsx)** - **问题描述**:代码无法同时兼容 `.xls` 和 `.xlsx` 格式。 - **解决方法**: - 根据文件扩展名选择 `HSSFWorkbook`(xls)或 `XSSFWorkbook`(xlsx): ```java if (fileName.endsWith(".xlsx")) { workbook = new XSSFWorkbook(inputStream); } else { workbook = new HSSFWorkbook(inputStream); } ``` #### 6. **公式未计算导致值错误** - **问题描述**:读取含公式的单元格时得到公式本身而非计算结果。 - **解决方法**: - 强制计算公式结果: ```java FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); CellValue cellValue = evaluator.evaluate(cell); ``` --- ### 示例代码片段 **导出Excel(使用SXSSFWorkbook)** ```java SXSSFWorkbook workbook = new SXSSFWorkbook(100); Sheet sheet = workbook.createSheet("数据"); for (int i = 0; i < 10000; i++) { Row row = sheet.createRow(i); Cell cell = row.createCell(0); cell.setCellValue("Row " + i); } try (FileOutputStream fos = new FileOutputStream("output.xlsx")) { workbook.write(fos); } finally { workbook.dispose(); // 清理临时文件 } ``` ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值