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