针对大Excel文件(如超过百万行)的读取,传统的一次性加载到内存的方式会导致 内存溢出(OOM),需采用 流式读取(Streaming) 或 分块读取(Chunk) 的策略。以下是具体方案及优化建议:
一、核心解决方案
1. 使用 Apache POI 的 SAX 模式(事件驱动)
- 适用场景:处理
.xlsx
文件(不支持.xls
),逐行解析避免内存溢出。 - 代码示例(Java):
import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.usermodel.XSSFSheetXMLHandler; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory;public class BigExcelReader {public void read(String filePath) throws Exception {try (OPCPackage pkg = OPCPackage.open(filePath)) {XSSFReader reader = new XSSFReader(pkg);XMLReader parser = XMLReaderFactory.createXMLReader();// 自定义 Sheet 处理器(需实现 XSSFSheetXMLHandler.SheetContentsHandler)SheetHandler handler = new SheetHandler();parser.setContentHandler(new XSSFSheetXMLHandler(reader.getStylesTable(), null, handler, false));InputSource sheetSource = new InputSource(reader.getSheetsData().next());parser.parse(sheetSource);}}// 自定义处理器(处理每一行数据)private static class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {@Overridepublic void row(int rowNum, List<Object> rowData) {// 处理行数据(rowData 是单元格值的列表)System.out.println("Row " + rowNum + ": " + rowData);}// 其他方法(如 startRow、endRow)可空实现} }
- 优点:内存占用极低(约几十MB),适合处理超大文件。
- 缺点:需要手动处理复杂的单元格格式和公式。
2. 使用 EasyExcel(阿里开源的流式读取库)
- 适用场景:简化流式读取,支持
.xlsx
和.xls
。 - 步骤:
- 添加依赖(Maven):
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.2</version> </dependency>
- 定义数据模型:
@Data // Lombok 注解,生成 getter/setter public class UserData {@ExcelProperty("姓名") // 对应表头private String name;@ExcelProperty("年龄")private Integer age; }
- 流式读取:
public class ReadBigExcel {public static void main(String[] args) {String fileName = "large_file.xlsx";// 逐行读取(每读一行触发一次监听器)EasyExcel.read(fileName, UserData.class, new AnalysisEventListener<UserData>() {@Overridepublic void invoke(UserData data, AnalysisContext context) {// 处理单行数据(如存入数据库)System.out.println(data);}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {System.out.println("读取完成");}}).sheet().doRead();} }
- 添加依赖(Maven):
- 优点:API 简单,内存可控(默认每批次读取1000行),支持数据模型映射。
- 缺点:无法直接操作单元格样式。
3. 分页读取(结合 POI 的 XSSF 和 SXSSF)
- 适用场景:需要分批次处理数据(如导出到数据库)。
- 示例代码:
Workbook workbook = WorkbookFactory.create(new File("large_file.xlsx")); Sheet sheet = workbook.getSheetAt(0); int pageSize = 1000; // 每页读取1000行 int totalRows = sheet.getPhysicalNumberOfRows();for (int page = 0; page < (totalRows + pageSize - 1) / pageSize; page++) {int startRow = page * pageSize;int endRow = Math.min(startRow + pageSize, totalRows);for (int i = startRow; i < endRow; i++) {Row row = sheet.getRow(i);// 处理行数据}// 手动触发垃圾回收(可选)System.gc(); }
- 优点:代码简单直观。
- 缺点:不适合超大文件(如百万行以上),内存仍可能溢出。
4. 转换为 CSV 处理
- 适用场景:不需要保留 Excel 格式(如公式、样式)。
- 步骤:
- 使用工具(如 Apache POI)将 Excel 转换为 CSV。
- 用 OpenCSV 或 Commons CSV 流式读取 CSV。
- 优点:CSV 处理速度快,内存占用低。
- 缺点:丢失 Excel 特有功能(如多Sheet、公式)。
二、优化技巧
1. 内存优化
- 禁用公式计算:
Workbook workbook = WorkbookFactory.create(file, null, true); // 避免缓存公式结果
- 关闭缓存:
FileInputStream fis = new FileInputStream(file); StreamingReader reader = StreamingReader.builder().rowCacheSize(100) // 缓存行数.bufferSize(4096) // 缓冲区大小.open(fis);
2. 性能优化
- 跳过空行和隐藏行:在处理器中判断行数据是否为空。
- 多线程处理:将读取的数据分块后提交到线程池处理(注意线程安全)。
3. 异常处理
- 捕获处理异常:避免因某一行数据错误导致整个任务中断。
try {// 处理行数据 } catch (Exception e) {e.printStackTrace(); // 记录错误日志 }
三、工具推荐
工具/库 | 适用场景 | 特点 |
---|---|---|
Apache POI | 需要精细控制Excel格式 | 功能全,但内存消耗高 |
EasyExcel | 快速处理数据(无需复杂格式) | 内存低,API简单 |
OpenCSV | 处理纯数据(非Excel特有功能) | 速度极快 |
Pandas | Python环境下的数据分析 | 适合中小型文件(需优化) |
四、实战建议
- 预处理文件:拆分大文件为多个小文件(如按Sheet或行数拆分)。
- 监控内存:使用 JVisualVM 或 Arthas 监控内存使用情况。
- 日志记录:记录处理进度和错误行号,便于排查问题。
- 测试极限:在预发布环境测试文件的最大承载能力。
通过以上方法,可有效处理GB级Excel文件,避免内存溢出问题。