【问题标题】:OutofMemoryException while reading large XML file in C# windows forms在 C# windows 窗体中读取大型 XML 文件时出现 OutofMemoryException
【发布时间】:2018-03-06 20:48:43
【问题描述】:

我一直将 .png 文件存储到 XML 元素之一,如下所示。我一直在将图像文件转换为 base64,然后将该字符串复制到 XML。我还能够将这个 XML 文件读取并加载到我的窗口窗体中。我面临的问题是随着 XML 文件随着节点的增加而增长,XML 文件的大小变得太大,现在它是 300 MB。当 Windows 窗体试图读取这个大型 XML 文件时,我收到了OutOfMemoryExceptions。 下面是我的xml文件的sn-p。

<TestResult>
    <ResultsID>49</ResultsID>
    <DateExecuted>2018-02-20T09:36:12.787</DateExecuted>
    <UserExecuted>xxx</UserExecuted>
    <CorrectedMean>1966.32245</CorrectedMean>
    <CorrectedVariance>19525.6632019949</CorrectedVariance>
    <TestPassed>true</TestPassed>
    <TestResultImage>Qk2.......</TestResultImage>
</TestResult>

我一直在尝试使用以下代码将 XML 加载到 .net 中

XDocument xmlResultsDoc = XDocument.Load("MeanData.xml");

并存储到我的模型类中,如下所示。

List<MeanVarianceTestResultDataList = 
(xmlResultsDoc.Descendants("TestResult").Select(m => new 
MeanVarianceTestResultsData()
                    {
                        ResultsID = 
Convert.ToInt32(m.Element("ResultsID").Value),
                        DateExecuted = 
Convert.ToDateTime(m.Element("DateExecuted").Value),
                        UserExecuted = 
Convert.ToString(m.Element("UserExecuted").Value),
                        CorrectedMean = 
Convert.ToString(m.Element("CorrectedMean").Value),
                        CorrectedVariance = 
Convert.ToString(m.Element("CorrectedVariance").Value),
                        TestPassed = 
Convert.ToBoolean(m.Element("TestPassed").Value),
                        TestResultImage =  

Convert.FromBase64String(
Convert.ToString(m.Element("TestResultImage").Value))
                    })).ToList();

【问题讨论】:

  • 考虑使用 SAX api。

标签: c# xml winforms linq-to-xml


【解决方案1】:

对于大型 xml 文件,您必须使用 XmlReader 来防止内存不足错误。试试下面的代码,它使用了 xml linq 和 XmlReader 的组合

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            XmlReader reader = XmlReader.Create(FILENAME);

            while (!reader.EOF)
            {
                if (reader.Name != "TestResult")
                {
                    reader.ReadToFollowing("TestResult");
                }
                if (!reader.EOF)
                {
                    XElement testResult = (XElement)XElement.ReadFrom(reader);
                    string image = (string)testResult.Element("TestResultImage"); 
                }
            }
        }
    }
}

【讨论】:

  • 谢谢。我还有一个问题。我的大型 xml 文件的一个原因是由于我将图像保存到其中一个元素中。为了存储这个图像,我首先将图像转换为 byte[],然后使用代码 Convert.ToBase64String(TestResultData.TestResultImage)) 将其转换为 Base64 字符串。这里 TestResultImage 是字节[]。似乎将 byte[] 转换为 Base64 字符串导致我的 XML 文件膨胀。 Base64 是否有其他工作可以将 byte[] 存储到 XML 中?
  • Base64String 进行压缩,所以它可能是最好的方法。也许序列化图像会提供更好的压缩。
【解决方案2】:

如果您的 XML 文件太大而无法一次加载到内存中,您可以使用 XmlReader 流式传输文件并一次仅加载一小部分。此外,对于包含非常大的 Base64 编码二进制数据的&lt;TestResultImage&gt; 元素,您可以使用XmlReader.ReadElementContentAsBase64(Byte[], Int32, Int32) 以增量方式读取数据块并将它们复制到一些Stream 中。

下面的代码展示了如何做到这一点:

//https://stackoverflow.com/questions/49159697/deserializing-json-child-object-values-into-parent-object-using-jsonconvert-dese

public class MeanVarianceTestResultsData
{
    public int ResultsID { get; set; }
    public DateTime DateExecuted { get; set; }
    public string UserExecuted { get; set; }
    public string CorrectedMean { get; set; }
    public string CorrectedVariance { get; set; }
    public bool TestPassed { get; set; }

    public string TestResultImageFile { get; set; }
    public Stream TestResultImage { get; set; }
}

public static class MeanVarianceTestResultsDataExtensions
{
    public static List<MeanVarianceTestResultsData> ReadResultListFrom(XmlReader reader, Func<MeanVarianceTestResultsData, Stream> openStream, Func<Stream, Stream> closeStream)
    {
        return reader.ReadSubtrees("TestResult").Select(r => ReadResultFrom(r, openStream, closeStream)).ToList();
    }

    public static MeanVarianceTestResultsData ReadResultFrom(XmlReader reader, Func<MeanVarianceTestResultsData, Stream> openStream, Func<Stream, Stream> closeStream)
    {
        if (reader == null || openStream == null)
            throw new ArgumentNullException();
        reader.MoveToContent();
        var result = new MeanVarianceTestResultsData();
        var isEmpty = reader.IsEmptyElement;
        // Read the root
        reader.Read();
        if (isEmpty)
            return result;
        while (!reader.EOF)
        {
            if (reader.NodeType == XmlNodeType.EndElement)
            {
                reader.Read();
                break;
            }
            else if (reader.NodeType != XmlNodeType.Element)
                // Comment, text, CDATA, etc.
                reader.Skip();
            else if (reader.Name == "ResultsID")
                result.ResultsID = reader.ReadElementContentAsInt();
            else if (reader.Name == "DateExecuted")
                result.DateExecuted = reader.ReadElementContentAsDateTime();
            else if (reader.Name == "UserExecuted")
                result.UserExecuted = reader.ReadElementContentAsString();
            else if (reader.Name == "CorrectedMean")
                result.CorrectedMean = reader.ReadElementContentAsString();
            else if (reader.Name == "TestPassed")
                result.TestPassed = reader.ReadElementContentAsBoolean();
            else if (reader.Name == "TestResultImage")
                result.TestResultImage = reader.ReadElementContentAsStream(() => openStream(result), closeStream);
            else
                reader.Skip();
        }
        return result;
    }
}

public static class XmlReaderExtensions
{
    public static Stream ReadElementContentAsStream(this XmlReader reader, Func<Stream> openStream, Func<Stream, Stream> closeStream)
    {
        if (reader == null || openStream == null)
            throw new ArgumentNullException();
        Stream stream = null;
        try
        {
            stream = openStream();
            byte[] buffer = new byte[4096];
            int readBytes = 0;
            while ((readBytes = reader.ReadElementContentAsBase64(buffer, 0, buffer.Length)) > 0)
            {
                stream.Write(buffer, 0, readBytes);
            }
        }
        finally
        {
            if (closeStream != null && stream != null)
                stream = closeStream(stream);
        }
        return stream;
    }

    public static IEnumerable<XmlReader> ReadSubtrees(this XmlReader reader, string name)
    {
        while (reader.ReadToFollowing(name))
        {
            using (var subReader = reader.ReadSubtree())
                yield return subReader;
        }
    }
}

然后,您可以按如下方式使用它将每个TestResultImage 图像读入MemoryStream

List<MeanVarianceTestResultsData> results;
using (var reader = XmlReader.Create(fileName))
{
    results = MeanVarianceTestResultsDataExtensions.ReadResultListFrom(reader, m => new MemoryStream(), s => { s.Position = 0; return s; });
}

这将通过完全跳过图像的中间 Base64 字符串表示来节省大量内存 - 但它仍然会为每个 MemoryStream 使用大量内存。或者,您可以将图像流式传输到一些临时文件中以供以后使用,例如通过执行以下操作:

List<MeanVarianceTestResultsData> results;
using (var reader = XmlReader.Create(fileName))
{
    results = MeanVarianceTestResultsDataExtensions.ReadResultListFrom(
        reader,
        m => { m.TestResultImageFile = Path.GetTempFileName(); return File.Open(m.TestResultImageFile, FileMode.Create); },
        s => { s.Dispose(); return null; });
}

在这种情况下,每个流在图像写入后被释放,文件名存储在MeanVarianceTestResultsData 中。 (当然,如果您打算在反序列化后立即处理流,则可以让流保持打开状态。)

示例fiddle

【讨论】:

  • 优秀!。我一定会尝试使用上面的代码。谢谢你的朋友。
  • 您的逻辑很好,但不幸的是,我们决定不再将图像存储在 XML 文件中。由于 XML 文件每天都在增长,因此应用程序加载和处理这些文件的时间过长。相反,我们决定将 Image 的引用存储在 XML 元素中,并将实际图像存储在服务器硬盘上。这样,应用程序就可以非常快地加载 XML 文件了。
猜你喜欢
  • 2017-01-28
  • 2021-10-16
  • 1970-01-01
  • 2012-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多