别用loadfromcollection加载整个datatable,它会把全部数据、样式、公式、共享字符串表一次性塞进内存;10万行以上就大概率触发outofmemoryexception,峰值内存常超2gb。

直接结论:别用 LoadFromCollection 加载整个 DataTable,它会把全部数据、样式、公式、共享字符串表一次性塞进内存;10万行以上就大概率触发 OutOfMemoryException,峰值内存常超 2GB。
为什么 DataTable.LoadFromCollection 会爆内存
EPPlus 的 LoadFromCollection(包括传 DataTable)底层会遍历所有行+列,为每个单元格创建 ExcelRange 对象,并缓存共享字符串索引、数字格式、样式引用等元数据。更关键的是:它不写磁盘,只在内存中构建完整 DOM 树,直到调用 Save() 才序列化——这意味着整张表的 XML 节点树全程驻留内存。
常见错误现象:
- 导出 50 万行 × 20 列后,GC 压力飙升,
Gen2堆长期不回收 - 任务管理器显示进程私有工作集持续增长至 1.5GB+,最终抛
System.OutOfMemoryException - 即使加了
using和Dispose(),也无效——对象早被内部缓存强引用
用 ExcelPackage.Cells[row, col] 手动流式写入
绕过集合加载机制,自己控制行写入节奏,每写完一批(如 5000 行)就调用 worksheet.Cells.AutoFitColumns() 或跳过(避免实时计算),最后再 Save()。关键是:不触发任何自动渲染逻辑。
实操要点:
本文档主要介绍如何通过python对office excel进行读写操作,使用了xlrd、xlwt和xlutils模块。另外还演示了如何通过Tcl tcom包对excel操作。感兴趣的朋友可以过来看看
-
rowIndex从 1 开始自增,每写一行就rowIndex++,不要用LoadFromCollection隐式推断起始位置 - 禁用公式自动计算:
package.Workbook.CalcMode = ExcelCalcMode.Manual - 关闭样式缓存:
worksheet.Cells.StyleID = -1(慎用,仅当全列无样式时) - 字符串值必须显式转为
string,避免DBNull.Value或DateTime混入同一列——否则 EPPlus 会降级整列为文本且丢失NumberFormat - 示例片段:
int rowIndex = 1; foreach (DataRow dr in dataTable.Rows) { worksheet.Cells[rowIndex, 1].Value = dr["Name"] as string ?? ""; worksheet.Cells[rowIndex, 2].Value = dr["Amount"] as double?; rowIndex++; if (rowIndex % 5000 == 0) GC.Collect(); // 主动提示 GC(非强制,但可缓解 Gen2 压力) }
共享字符串表(SST)必须手动注册
EPPlus 默认对每个字符串都新建 SST 条目,10 万行含重复姓名/状态码时,文件体积暴涨 3–5 倍,且内存占用翻倍。你得自己维护一个 Dictionary<string int></string> 映射表,在写入前查重并复用索引。
为什么不能跳过:
- 不注册就直接赋值
cell.Value = "已发货"→ EPPlus 内部新建 SST 项,哪怕已有 99999 个相同值 - 注册后写入需用
cell.SetValue<string>(sharedStringIndex)</string>(注意不是.Value =) - 注册时机必须在写入前,且需确保
SharedStringTablePart已初始化(可通过worksheet.Workbook.SharedStrings访问) - 若用 Open XML SDK 底层写,必须手动生成
<sst></sst>节点并插入WorkbookPart,否则 Excel 打开报错
比 EPPlus 更彻底的方案:OpenXmlWriter 流式生成
当数据量稳定超百万行、且对文件体积和内存零容忍时,EPPlus 的“半流式”仍不够。此时应放弃所有封装,用 OpenXmlWriter 直接向 WorksheetPart.GetStream(FileMode.Create) 写原始 XML 节点。
关键约束:
- 必须预先注册所有字符串到
SharedStringTablePart,否则 Excel 无法解析 - 每行写入需构造完整
<row r="1"><c r="A1" t="s"><v>0</v></c></row>,索引v是 SST 中的位置 - 数字列要区分
t="n"(数值)、t="d"(日期),不能全用t="s" - 没有自动换行、自动列宽、公式支持——这些都得你自己算好再写进 XML
- 优点是内存恒定在 ~20MB 内,导出 200 万行耗时通常低于 8 秒(SSD 环境)
真正难的从来不是写多少行,而是怎么让每一行都“不回头”:不查历史、不建索引、不缓存上下文。一旦你开始为某一行去查另一行的样式或合并状态,流式就破功了。










