c# - 使用 Open XML SDK 流式传输 Excel 数据
问题描述
我们有一个相当大的 Excel 工作簿。大约 3,300 列和数千行。
我们发现,尝试对数据执行任何操作都会导致内存使用量很高,大约为 3 GB。
似乎该DocumentFormat.OpenXml
包在迭代时将工作表的完整对象结构保留在内存中。一般来说,我们这样做:
var workbookPart = _document.WorkbookPart;
var worksheets = workbookPart.Workbook.Descendants<Sheet>();
foreach(var worksheet in worksheets)
{
var worksheetPart = (WorksheetPart) workbookPart.GetPartById(worksheet.Id);
foreach(var row in worksheetPart.Worksheet.Descendants<Row>())
{
foreach(var cell in row.Descendants<Cell>())
{
var (_, value) = ParseCell(cell);
}
}
}
ParseCell
只需Cell
通过从工作簿上查找字符串值来获取 的内容SharedStringTable
,或者如果它是一个数字,则解析该数字。
简单地运行这个对结果没有任何作用的代码ParseCell
仍然会占用大量内存。
当我们分析这段代码时,我们注意到,尽管我们尽最大努力使用API 来避免在内存中拥有大量集合,Cell
但工作表中的每个单元格都有一个堆。IEnumerable<T>
这与此 Nuget 包的推荐用法非常接近。
从分析来看,问题似乎是每个Cell
都对下一个有很强的引用Cell
,对于Row
.
每个Cell
都有一个名为的字段,_next
这就是使每个 Cell 具有强根的原因。单元格 A 对单元格 B、B 到 C、C 到 D 有很强的引用。
Row
具有类似的结构,其中第 0_next
行对第 1 行有一个字段,依此类推,所以对于Row
我们经历的每一个,它都保持对下一个的强引用Row
。
所以一切都联系在一起。当我在处理完最后一个之后用 WinDbg 查看它时,堆上的 s数量与工作簿包含Row
的完全相同。Cell
!dumpheap -stat
我们使用此 SDK 的方式不会扩展到更多行。有没有办法更有效地使用这个包并逐行处理工作表,而不会将整个工作表的对象图保存在内存中?
解决方案
这里一个合适的解决方案是使用OpenXmlReader
XML 阅读器。另一个关键是使用Elements
而不是Decendents
避免在 XML 结构中看得太深。
using (var reader = OpenXmlReader.Create(worksheetPart))
{
while (reader.Read())
{
if (typeof(Row).IsAssignableFrom(reader.ElementType))
{
var row = (Row)reader.LoadCurrentElement();
foreach (var cell in row.Elements<Cell>())
{
var (_, value) = ParseCell(cell);
}
}
}
}
这确实“流”了元素,并且内存使用量很小。
推荐阅读
- xml - 如何将 XML 添加为另一个 XML 中的内部文本,作为 SOAP 请求的参数
- c# - Linq To Sql 加入动态结构化列表
- awk - 如何通过 awk 或 sed 从不同的行获取值?
- dataweave - 如何在dataweave中降序排序?
- python - Python中的顺序合并排序(非递归合并)
- vue.js - Vuetify v-data-table 不会增加字体大小
- java - Add Parameters to Google Cloud Tasks
- reactjs - 如果某些浏览器中的某些列是固定的,为什么 Ant 表中的表头和数据重叠?
- javascript - 使用 Date javascript 转换时间戳数组
- javascript - Javascript密码生成器如何确保密码符合标准