很好的问题!这两种方式的核心区别在于解析模式 和内存占用。让我详细对比一下:
两种解析方式的本质区别
第一种方式(SAX 流式解析)
java
pkg = OPCPackage.open(data);
XSSFReader reader = new XSSFReader(pkg);
XMLReader parser = XMLReaderFactory.createXMLReader(...);
parser.parse(sheetSource);
- SAX (Simple API for XML):事件驱动,边读边处理
- 内存占用:极低,只保留当前处理的数据
- 处理速度:快,不需要等待整个文件加载完成
第二种方式(DOM 解析)
java
Workbook workbook = WorkbookFactory.create(inputStream);
Sheet sheet = workbook.getSheetAt(0);
- DOM (Document Object Model):把整个文档树加载到内存
- 内存占用 :非常高,整个 Excel 文件都在内存中
- 处理速度:慢,需要先加载整个文件
内存占用对比表
| 文件大小 | SAX 方式 | DOM 方式 |
|---|---|---|
| 1 MB | ~10 MB | ~50 MB |
| 10 MB | ~20 MB | ~500 MB |
| 100 MB | ~50 MB | ~5 GB+ ⚠️ |
为什么推荐第一种方式
1. 内存安全
java
// 第二种方式的风险
Workbook workbook = WorkbookFactory.create(inputStream);
// 如果文件是 100MB,内存可能暴涨到 5GB+,直接 OOM!
2. 适合大文件场景
- Excel 导入功能通常处理几千到几十万行数据
- 使用 SAX 方式可以轻松处理百万行数据
- 使用 DOM 方式可能 5 万行就内存溢出
3. 响应更快
java
// SAX:立即开始处理第一行
parser.parse(sheetSource); // 边读边处理
// DOM:必须等待整个文件加载完
Workbook workbook = WorkbookFactory.create(inputStream); // 等待...
Sheet sheet = workbook.getSheetAt(0); // 等待...
第二种方式什么时候可以用?
虽然第一种方式更好,但第二种方式在以下场景也可以用:
- 小文件场景(< 1MB,< 1000行)
- 需要随机访问(比如跳到第 1000 行再回来看第 10 行)
- 需要修改 Excel 文件(SAX 是只读的)
- 读写混合操作(比如读取数据后修改某些单元格)
实际案例对比
假设用户上传一个 50MB 的 Excel 文件(约 10 万行数据):
使用第二种方式(DOM)
1. 加载文件到内存 → 内存占用 2GB+
2. 读取数据 → 继续占用内存
3. 如果并发 5 个用户 → 10GB+ 内存
4. 结果:服务器 OOM,服务崩溃 ❌
使用第一种方式(SAX)
1. 流式读取 → 内存占用 50MB
2. 边读边处理 → 内存稳定
3. 并发 5 个用户 → 250MB 内存
4. 结果:稳定运行 ✅
总结
| 维度 | SAX 方式(推荐) | DOM 方式(谨慎使用) |
|---|---|---|
| 内存占用 | ⭐⭐⭐⭐⭐ 极低 | ⭐ 极高 |
| 处理大文件 | ⭐⭐⭐⭐⭐ 优秀 | ⭐ 容易 OOM |
| 读取速度 | ⭐⭐⭐⭐⭐ 快 | ⭐⭐⭐ 慢 |
| 随机访问 | ⭐ 不支持 | ⭐⭐⭐⭐⭐ 支持 |
| 代码复杂度 | ⭐⭐⭐ 稍复杂 | ⭐⭐⭐⭐⭐ 简单 |
结论 :如果你的场景是批量导入大 Excel 文件 ,一定要用第一种 SAX 方式。如果是小文件且需要随机访问,第二种方式也可以考虑。