ReadOuterXml抛出OutOfMemoryException,读取大(1 GB)XML文件的一部分

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (1)
  • 关注 (0)
  • 查看 (37)

我正在处理一个大型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。

提问于
用户回答回答于

你得到一个最可能的原因OutOfMemoryExceptionReadOuterXml()是您要的1 GB XML文档的很大一部分读入一个字符串,并击中在.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(然后再次解析)。此版本的算法完全省略了这项额外的工作。它还避免了创建大到足以进入大对象堆的字符串,这可能导致其他性能问题。

如果仍然使用太多内存,则需要为XML 实现类似SAX的解析,一次只加载一个元素<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.
    }
}

扫码关注云+社区

领取腾讯云代金券