You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Open XML SDK处理大Excel内存过高,求高效行级处理方案

我之前在处理超大型Excel文件的时候遇到过完全一样的问题——DocumentFormat.OpenXml SDK默认的Descendants<T>遍历方式会悄悄构建整个工作表的对象树,而Cell和Row的_next强引用链会把所有对象都拴在内存里,GC根本收不走,内存直接飙到几个G都很正常。

要解决这个问题,核心思路是绕开SDK的对象树构建,直接用流式XML解析,这样每次只处理当前节点,不会把整个工作表都留在内存里。下面是具体的实现方案:

第一步:提前加载共享字符串表(SharedStringTable)

因为大部分字符串值都存在共享字符串表里,提前把它加载到一个字典里缓存起来,后续解析单元格的时候直接查表就行,不用每次去读XML,效率更高:

private Dictionary<int, string> LoadSharedStrings(WorkbookPart workbookPart)
{
    var sharedStringPart = workbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
    if (sharedStringPart?.SharedStringTable == null)
    {
        return new Dictionary<int, string>();
    }

    return sharedStringPart.SharedStringTable.Elements<SharedStringItem>()
        .Select((item, index) => new { Index = index, Text = item.InnerText })
        .ToDictionary(x => x.Index, x => x.Text);
}

第二步:用XmlReader流式遍历工作表

不要用worksheetPart.Worksheet.Descendants<Row>(),而是直接打开工作表的XML流,用XmlReader逐节点解析,这样只会把当前处理的节点留在内存里:

var workbookPart = _document.WorkbookPart;
var sharedStrings = LoadSharedStrings(workbookPart);

foreach (var sheet in workbookPart.Workbook.Descendants<Sheet>())
{
    var worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id);
    
    // 直接读取工作表的XML流,避免构建完整对象树
    using (var reader = XmlReader.Create(worksheetPart.GetStream()))
    {
        while (reader.Read())
        {
            // 找到<row>节点,开始处理一行
            if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "row")
            {
                ProcessRow(reader, sharedStrings);
            }
        }
    }
    
    // 处理完当前工作表后,手动删除Part,强制释放资源
    workbookPart.DeletePart(worksheetPart);
}

第三步:实现行和单元格的解析逻辑

在ProcessRow方法里,继续用XmlReader遍历当前行下的<c>(单元格)节点,解析每个单元格的值:

private void ProcessRow(XmlReader reader, Dictionary<int, string> sharedStrings)
{
    // 遍历当前row节点的子节点
    while (reader.Read())
    {
        // 遇到row的结束节点,退出当前行的处理
        if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "row")
        {
            break;
        }
        
        // 找到cell节点
        if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "c")
        {
            var cellType = reader.GetAttribute("t"); // 获取单元格类型(s=共享字符串,n=数字等)
            var cellValue = string.Empty;
            
            // 读取单元格的<v>节点(值)
            if (reader.ReadToDescendant("v"))
            {
                cellValue = reader.ReadElementContentAsString();
            }
            
            // 解析最终值
            var finalValue = ParseCellValue(cellType, cellValue, sharedStrings);
            
            // 这里写你的业务逻辑,比如把值写入数据库、生成报表等
            // ...
        }
    }
}

private string ParseCellValue(string cellType, string cellValue, Dictionary<int, string> sharedStrings)
{
    if (string.IsNullOrEmpty(cellValue))
    {
        return string.Empty;
    }
    
    switch (cellType)
    {
        case "s": // 共享字符串
            if (int.TryParse(cellValue, out var stringIndex) && sharedStrings.TryGetValue(stringIndex, out var sharedText))
            {
                return sharedText;
            }
            break;
        case "n": // 数字
            // 可以根据需求转换成int/double,或者直接返回字符串
            return cellValue;
        case "b": // 布尔值
            return cellValue == "1" ? "TRUE" : "FALSE";
        case "d": // 日期
            if (double.TryParse(cellValue, out var dateNum))
            {
                // 注意:OpenXml的日期是OLE自动化日期,从1900-01-01开始计算天数,存在闰年bug
                return DateTime.FromOADate(dateNum).ToString("yyyy-MM-dd HH:mm:ss");
            }
            break;
    }
    
    return cellValue;
}

为什么这个方案能解决内存问题?

  • 流式解析:XmlReader是向前读取的,每次只加载当前节点到内存,处理完就可以被GC回收,不会构建完整的Row/Cell对象树,也就不会产生那个导致内存泄漏的强引用链。
  • 手动释放资源:处理完每个工作表后调用DeletePart,强制SDK释放该工作表的所有资源,避免内存占用累积。
  • 共享字符串缓存:提前加载SharedStringTable,既提升了解析效率,又避免了反复读取XML的开销。

额外注意事项

  • 如果你的Excel有大量格式或者注释,XmlReader会忽略这些非数据节点,只处理行和单元格,进一步减少内存占用。
  • 对于极端大的文件(比如几十万行),可以考虑在处理行的时候批量写入数据(比如每处理1000行就批量插入数据库),避免业务逻辑本身带来的内存压力。
  • 确保在使用完XmlReader后及时释放(用using语句),避免流泄漏。

内容的提问来源于stack exchange,提问作者vcsjones

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 09:08:57