内存中的非常大的字符串

10

我正在编写一个程序,用于将数百MB的字符串数据(接近1GB)格式化为XML,并要求将其作为HTTP(GET)请求的响应返回。

我在循环中使用StringWriter/XmlWriter来构建记录的XML,并返回该XML。

using (StringWriter writer = new StringWriter())
using (writer = XmlWriter.Create(writer, settings)) //where settings are the xml props

writer.ToString() 
在测试过程中,我遇到了一些内存不足异常,对于如何找到解决方案感到困惑。你们有没有关于优化响应内存的建议?是否有一种内存效率更高的编码方式?或者分块数据是否可行——我只是无法想象如何在不将整个数据构建为一个巨大的字符串对象的情况下返回它。
谢谢!
-- 一些澄清 -- 这是一个基于 ASP.NET WebServices 应用程序,连接速度为千兆以太网,正如 Josh 所指出的那样。由于我并不太熟悉它,所以还有一些学习曲线。
我正在使用 XMLWriter 创建 XML 并将其转换为字符串。
一些统计数据:响应 XML 大小约为 385 MB(我的数据大小将很快增长到更多),通过内存分析器计算的字符串对象大小峰值为 605MB。感谢所有回答的人......

9
HTTP响应中有1GB的XML?真的吗? - Mitch Wheat
我很想说“扔些硬件进去”(开玩笑)。每个客户端500 MB(或350 MB或1GB)不可扩展。也许您可以更详细地介绍一下您正在生成的XML。这个链接http://msdn.microsoft.com/en-us/library/aa528818.aspx可能会有所帮助。 - PRR
多么有见地啊--我相信这就是管理层选择的解决方案 :) - bushman
6个回答

6

使用XmlTextWriter包装Reponse.OutputStream将XML发送到客户端,并定期刷新响应。这样,您永远不必在任何时候内存中保存超过几MB的数据(至少是用于发送到客户端的数据)。


听起来他正在使用字符串操作来组装XML。 - SLaks
1
@SLaks,听起来他可能在做任何事情,因为他没有说清楚。 - Samuel Neff
甚至可以将 XmlTextWriter 包装在一个包装了 Response.OutputStreamGZipStream 中。 - Steven

5

你不能直接将响应流传输到客户端吗?XmlWriter不需要在内存中缓冲其底层流。如果是ASP.NET,您可以使用Response.OutputStream;如果是WCF,则可以使用响应流


Josh,这是一个Web服务项目--客户端通过GET请求到Web方法--作为对Microsoft解决方案的新手,我不确定是否可能以及如何实现。 - bushman
使用 ASMX Web 服务可能不太可能实现,如果您正在使用它,我强烈建议在重新设计变得太困难之前立即转向 WCF。您已经遇到了 ASMX 的限制之一,而且还有很多其他限制,不仅限于性能。但是,使用 WCF,您可以返回 Stream 对象并将数据分块传输给调用者。这都是内置的。 - Josh
谢谢Josh!大家都提供了很好的意见,但我会将这个标记为正确答案。 - bushman

2

我遇到了类似的问题,希望这可以帮到其他人。我的初始代码如下:

var serializer = new XmlSerializer(type);
string xmlString;

using (var writer = new StringWriter())
{
    serializer.Serialize(writer, objectData, sn); // OutOfMemoryException here
    xmlString = writer.ToString();
}

我最终用MemoryStream替换了StringWriter,这解决了我的问题。

using (var mem = new MemoryStream())
{
    serializer.Serialize(mem, objectData, sn);
    xmlString = Encoding.UTF8.GetString(mem.ToArray());
}

2

1GB的HTTP get请求?这太多了!也许你应该重新考虑一下。至少压缩输出可以有所帮助。


1
如果仍然以这种方式构建XML,则Gzipping将无济于事。问题不在于数据的传输,而在于在发送之前将其缓冲在内存中的事实。对于HTTP GET没有实际限制,特别是考虑到您可以恢复中断的下载,并且我们不知道他正在跨越什么类型的网络。可能是千兆以太网! - Josh

2

不应使用字符串操作创建XML。

相反,您应该使用XmlTextWriterXmlDocument或(在 .Net 3.5 中)XElement类在内存中构建XML树,然后使用XmlTextWriter直接将其写入Response.OutputStream

直接向包装Response.OutputStreamXmlTextWriter写入将是最有效的(您永远不会一次性拥有整个元素树),但会稍微复杂一些。

通过这种方式,您永远不会有一个包含整个对象的单个字符串(或数组),因此应避免OutOfMemoryExceptions。


1
XmlDocument和XElement将表现出与字符串操作相同(实际上更糟糕)的内存问题!构建如此大的XML结构只能使用XmlWriter API完成。 - Josh
1
如果内存不足,仍然可能会出现内存异常。更改为写入流的方法似乎更合理。 - Eric J.
@Josh:不是的。只有StringArray会分配大块_连续_内存。 - SLaks
这是正确的,但是一个XmlDocument/XElement所消耗的总内存将比它的字符串构建器副本要大得多。无论哪种方式,在内存中持有那么多数据只会引发DoS攻击。 - Josh
@JoshοΦöφ‰·γö³οΦ¨δΫÜφ‰·XmlTextWriterεè·ηÉΫεΨàιöΨιIJεΚîψIJ - SLaks
1
不开玩笑,我讨厌那个API。但是当处理这么大的数据时,开发人员只需要咬紧牙关,按照正确的方式去做,否则某一天会出现问题,通常就在你正在部署已经拖延了6个月的新计费系统时。 - Josh

1

你将需要在各自独立的 GET 请求中返回每个记录(或小组记录)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接