将大字符串转换为UTF-8

3

我有一个WCF4 REST服务,它查询数据库并返回JSON。一些用户想要进行非常大的查询,但是我很难返回这么长的字符串。例如,我需要返回一个500M的JSON字符串(所有数据都是ASCII文本),但当我尝试从.NET的本机UTF-16转换字符串时,会出现OutOfMemoryException异常。以下是我正在做的紧凑示例。

[WebInvoke(UriTemplate="/RunQuery", ResponseFormat=WebMessageFormat.Json)]
public Stream RunQuery() {
    // Perform query and return serialized json string (~500 million ASCII characters)
    string json = DoQuery(HttpContext.Current.Request.Form);
    // Set output charset
    WebOperationContext.Current.OutgoingResponse.ContentType = "application/json; charset=utf-8";
    // Convert UTF-16 string to UTF-8 (OutOfMemoryException)
    byte jsonBytes[] = System.Text.Encoding.UTF8.GetBytes(json)
    // Send UTF-8 string, without BOM
    return new MemoryStream(jsonBytes);
}

只有在JSON大小不超过200M左右时才能正常工作。在运行时,我看到IIS进程的内存使用量逐渐增加,然后在2.8G时爆炸并死机。堆栈跟踪报告称它发生在System.String.ToCharArray处。我尝试了将字符串分块以构建字节数组的各种变体,但似乎没有任何作用。有什么办法可以在不崩溃的情况下发送这些数据?


2
500M的JSON?哎呀。你不能将数据分成页面吗? - Marc Gravell
只是猜测:你可以尝试将响应数据分块写入临时文件,然后返回一个打开的流以供临时文件使用吗?(最好重新构建DoQuery(),使其支持将结果写入输出流而不是返回流。如果没有必要,确实不应该缓冲整个兆字节的数据。) - millimoose
或将数据流返回客户端... - Polity
现在我想知道是否有一种方法可以创建WCF服务,使您可以直接将输出流传递给您的代码,而不必返回输入流到响应中。 - millimoose
@Polity,你能指出实现这个功能的API吗? - millimoose
2
500 MB 的 Json 对于服务器和客户端来说都非常庞大。我会尝试将结果分块发送(http 支持此功能),每个块包含一个有效的 Json。类似于 Twitter 的流 API(https://dev.twitter.com/docs/streaming-api/methods)。 - L.B
2个回答

0

您可以编写自己的实现,以便在传输时动态将输入转换为utf8,从而解决此问题。

您应该能够通过提取输入字符串的部分并逐块将它们转换为utf 8来实现。

请记住,字节数不一定与字符计数相同,除非您从未发送任何类型的国际字符。


0

如果你需要返回流,请使用文件流或至少预分配MemoryStream空间。

如果你必须坚持使用500Mb的字符串:

  • 使用64位机器和64位进程。x86进程成功分配那么大的内存是不太可能的。请注意,即使您使用64位进程,CLR对“单块分配”大小有约2Gb的限制,使得1Gb的字符串不太可能适合内存。因此,在接近500Mb-1Gb的某个点上,切换到64位将不再有帮助。

  • 使用Writers - 它们可以轻松地(即http://msdn.microsoft.com/en-us/library/3aadshsx.aspx)直接编码输出,同时将JSON写入其中。作为额外的建议 - 不要创建JSON字符串,而是将输出写入Writer。

  • 如果你知道你的字符串只包含ASCII字符 - 通过自己写入流并将每个字符强制转换为字节来欺骗。


很不幸,我的服务必须是32位的,才能使用Oracle odp.net驱动程序(Oracle.DataAccess.dll),似乎没有64位版本。当我创建一个“new MemoryStream(500000000)”时,它会死掉,可能是因为我在内存中有一个500000000个字符的字符串。这确实绕过了整个字节数组,这有所帮助。JSON是由JavaScriptSerializer生成的,它给我提供了String或StringBuilder作为输出的选择。我希望我可以简单地返回我的序列化对象,但它使用了嵌套的字典... - Vimm
我看到了 - 在32位进程中处理大量结果时必须要非常小心,即同时进行两个这样的请求将会破坏你的服务器。考虑使用json.net serializer (json.codeplex.com),因为它比内置的序列化器(如- http://james.newtonking.com/projects/json/help/html/T_Newtonsoft_Json_JsonTextWriter.htm)更加灵活。否则我所看到的唯一选项就是编写自己的JSON序列化,并直接写入响应流... - Alexei Levenkov

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