【问题标题】:ReadOuterXml is throwing OutOfMemoryException reading part of large (1 GB) XML fileReadOuterXml 正在抛出 OutOfMemoryException 读取大型(1 GB)XML 文件的一部分
【发布时间】:2017-10-06 10:46:01
【问题描述】:

我正在处理一个大型 XML 文件,并且在运行应用程序时,XmlTextReader.ReadOuterXml() 方法正在引发内存异常。

代码行就像,

XmlTextReader xr = null;
try
{
    xr = new XmlTextReader(fileName);
    while (xr.Read() && success)
    {
        if (xr.NodeType != XmlNodeType.Element) 
            continue;
        switch (xr.Name)
        {
            case "A":
                var xml = xr.ReadOuterXml();
                var n = GetDetails(xml);
                break;
        }
    }
}
catch (Exception ex)
{
    //Do stuff
}

使用:

private int GetDetails (string xml)
{

    var rootNode = XDocument.Parse(xml);
    var xnodes = rootNode.XPathSelectElements("//A/B").ToList();
    //Then  working on list of nodes

}

现在在加载 XML 文件时,应用程序在 xr.ReadOuterXml() 行上抛出异常。可以做些什么来避免这种情况? XML 的大小几乎为 1 GB。

【问题讨论】:

  • 我们在这里讨论的 XML 文件的大小是多少?
  • 只是加载的 xml 的大小太大。您可以考虑使用 GetDetails 的迭代器和产生结果,这会保持低内存。
  • 嗨@Flater,XML 的大小几乎是 1 GB
  • 嗨@Fabio,是的,我可以使用迭代器,但这会影响我认为的性能
  • 编程总是在速度/性能和内存大小之间进行权衡。你需要选择一个

标签: c# xml


【解决方案1】:

您在ReadOuterXml() 中获得OutOfMemoryException 的最可能原因是您试图将1 GB XML 文档的大部分内容读入字符串,并且正在点击Maximum string length in .Net

所以,不要那样做。而是直接从XmlReader 加载,使用XDocument.Load()XmlReader.ReadSubtree()

using (var xr = XmlReader.Create(fileName))
{
    while (xr.Read() && success)
    {
        if (xr.NodeType != XmlNodeType.Element)
            continue;
        switch (xr.Name)
        {
            case "A":
                {
                    // ReadSubtree() positions the reader at the EndElement of the element read, so the 
                    // next call to Read() moves to the next node.
                    using (var subReader = xr.ReadSubtree())
                    {
                        var doc = XDocument.Load(subReader);
                        GetDetails(doc);
                    }
                }
                break;
        }
    }
}

然后在GetDetails() 做:

private int GetDetails(XDocument rootDocument)
{
    var xnodes = rootDocument.XPathSelectElements("//A/B").ToList();
    //Then  working on list of nodes
    return xnodes.Count;
}

这不仅会使用更少的内存,而且性能也会更高。 ReadOuterXml() 使用临时的 XmlWriter 将输入流中的 XML 复制到输出 StringWriter(然后您再解析一次)。这个版本的算法完全跳过了这个额外的工作。它还避免了创建足够大的字符串以继续large object heap,这可能会导致额外的性能问题。

如果这仍然占用太多内存,您将需要为您的 XML 实现 SAX-like 解析,此时您一次只加载一个元素 <B>。首先介绍如下扩展方法:

public static partial class XmlReaderExtensions
{
    public static IEnumerable<XElement> WalkXmlElements(this XmlReader xmlReader, Predicate<Stack<XName>> filter)
    {
        Stack<XName> names = new Stack<XName>();

        while (xmlReader.Read())
        {
            if (xmlReader.NodeType == XmlNodeType.Element)
            {
                names.Push(XName.Get(xmlReader.LocalName, xmlReader.NamespaceURI));
                if (filter(names))
                {
                    using (var subReader = xmlReader.ReadSubtree())
                    {
                        yield return XElement.Load(subReader);
                    }
                }
            }

            if ((xmlReader.NodeType == XmlNodeType.Element && xmlReader.IsEmptyElement)
                || xmlReader.NodeType == XmlNodeType.EndElement)
            {
                names.Pop();
            }
        }
    }
}

然后,如下使用:

using (var xr = XmlReader.Create(fileName))
{
    Predicate<Stack<XName>> filter =
        (stack) => stack.Peek().LocalName == "B" && stack.Count > 1 && stack.ElementAt(1).LocalName == "A";
    foreach (var element in xr.WalkXmlElements(filter))
    {
        //Then working on the specific node.
    }
}

【讨论】:

  • 谢谢@dbc...这真的很有帮助。
【解决方案2】:
using (var reader = XmlReader.Create(fileName))
{   
    XmlDocument oXml = new XmlDocument();
    while (reader.Read())
        {                  
            oXml.Load(reader);                    
        }
}

对我来说,当我们通过 XmlDocument 加载方法将其返回到 XmlDocument 时,上面的代码解决了这个问题

【讨论】:

  • 欢迎来到 SO。 command 是什么?你在文件中的阅读情况如何?在解决方案中发布代码时需要详细说明。
  • @ConnorLow 我已经用文件阅读器更新了代码,我在从数据库中获取数据时得到了“System.OutOfMemoryException”,因此添加了带有代码的 SQL 连接,但有些方法没有正确保存跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-20
  • 2015-04-24
  • 2012-03-31
  • 1970-01-01
  • 2017-10-02
  • 1970-01-01
  • 2016-11-23
相关资源
最近更新 更多