读取巨大的 xml 元素值后清理内存
本文关键字:内存 元素 巨大 xml 读取 | 更新日期: 2023-09-27 18:10:04
我很少求助于此,但这让我发疯:我正在读取一个 xml 文件,该文件包装了任意数量的项目,每个项目都有一个 b64 编码的文件(以及一些随附的元数据(。最初我只是将整个文件读入一个XmlDocument
,但是虽然这是更干净的代码,但我意识到文件大小没有限制,并且XmlDocument
消耗大量内存,如果文件足够大,可能会耗尽。所以我重写了代码以改用 XmlTextReader
,如果问题是程序被发送了一个带有大量合理大小附件的 xml 文件,这很好用......但是仍然有一个大问题,这就是我转向你的地方:
如果我的 xml 读取器位于 File 元素处,则该元素包含一个巨大的值(例如 500MB(,我调用 reader.ReadElementContentAsString()
,我现在有一个占用 500MB 的字符串(或者可能是 OutOfMemoryException(。在任何一种情况下,我想做的只是写到日志中,"该文件附件完全太大了,我们将忽略它并继续前进",然后移动到下一个文件。但是我刚刚尝试读取的字符串似乎并没有被垃圾回收,所以实际发生的是字符串占用了所有的 RAM,之后它尝试读取的所有其他文件也会抛出 OutOfMemoryException,即使大多数文件都非常小。
回想一下:此时,我正在将元素的值读取到本地字符串中,因此我本来希望它立即有资格进行垃圾回收(因此,最迟当程序尝试读取下一项并发现它没有可用内存时,它将被垃圾回收(。但是我已经尝试了一切,以防万一:将字符串设置为 null,调用显式GC.Collect()
......没有骰子,任务管理器指示 GC 只收集了大约 40k,其中 ~500MB 它刚刚请求存储字符串,我仍然会得到内存不足异常尝试读取其他任何内容。
似乎没有任何方法可以在不读取该元素的情况下使用 XmlTextReader
知道 xml 元素中包含的值的长度,所以我想我被困在读取字符串上......我是否遗漏了什么,或者真的没有办法从 XML 文件中读取一个巨大的值而不完全破坏程序之后进一步执行任何操作的能力?我快
我已经阅读了一些关于 C# 的 GC 和 LOH 的信息,但我读到的任何内容都不会向我表明这会发生......
如果您需要任何进一步的信息,请告诉我,谢谢!
编辑:我确实意识到该进程作为 32 位进程运行,这意味着它比应有的内存匮乏更多。修复了这个问题,这变得不那么成问题了,但它仍然是我想修复的行为。(需要更多和/或更大的文件才能达到抛出 OutOfMemoryException 的点,但一旦抛出它,我似乎仍然无法及时回收该内存。
我在使用用于将大文件作为 base64 字符串传输的 soap 服务时遇到了类似的问题。
当时我使用XDocument而不是XmlDocument,这对我来说是诀窍。
您可以使用 XmlReader.ReadValueChunk 方法一次读取一个"块"元素的内容,而不是尝试一次读取整个内容。例如,通过这种方式,您可以在某个时候确定数据太大,然后忽略它并记录事件。 StringBuilder
可能是将收集的字符数组块组合在一个字符串中的最佳方法。
如果要使用 GC.Collect()
释放内存,可以使用 GC.WaitForPendingFinalizers()
强制立即完成和释放内存。这可能会影响性能(甚至挂起,请参阅链接后面的说明(,但您应该摆脱大型对象,假设您不再有任何对它们的实时引用(即局部变量已经超出范围或其值设置为 null(并正常继续操作。当然,当内存消耗是一个问题并且您确实希望强制摆脱多余的内存分配时,您应该将其用作最后的手段。
我已经成功地在内存敏感环境中使用了GC.Collect();GC.WaitForPendingFinalizers();
组合,将应用程序的内存占用量保持在 100MB 以下,即使它读取一些非常大的 XML 文件 (>100MB(。为了提高性能,我还使用 Process.PrivateMemorySize64
来跟踪内存消耗,并仅在达到特定限制后强制完成。在我的改进之前,内存消耗有时会超过 1GB!
我不肯定这种情况,但我认为您需要处理XmlTextReader
.将过大节点后面的节点的 xmlpath 保存为字符串,将大量字符串设置为 null,然后释放XmlTextReader
并在大节点之后的节点上重新打开它。据我了解,如果您将字符串设置为 null
,或者它超出了范围,GC 应该尽快释放该内存。在我看来,您更有可能释放字符串,但您继续对现在保留大量内存的XmlTextReader
进行操作。
想到的另一个想法是尝试在unsafe
块中执行此操作,然后显式释放内存,但是,看起来这是不可能的(其他人可能知道,但在环顾四周后,似乎不安全的块仍然是GC'd,它只是给你指针(。另一种选择,尽管 imo 是一个糟糕的选择,是制作一个 dll 用于在 C 或 C++ 中解析并从 C# 项目中调用它。
在做任何像最后一个建议这样的疯狂事情之前,先尝试第一个建议:)