读取字符串时出现OutOfMemoryException异常

8

我有一个C# .NET系统,它接收一个JSON数据源并使用Newtonsoft.Json.JsonConvert.DeserializeObject转换器将其转换为对象。

只要JSON字符串大小不超过一定范围(几MB),这个过程就完美运行。但是一旦返回的数据很大(接近100MB),我就会遇到OutOfMemoryException错误。

这段代码对小数据非常有效:

// WebClient ------------------------------------------------------------------
var _client = new System.Net.WebClient();
var _content = _client.DownloadString(_url);

但是在最后一行(DownloadString)出现错误。

我尝试更改为以下内容,对于小数据也有效,但当数据增加时,仍会在ReadToEnd行出现错误。

using (var _response = (System.Net.HttpWebResponse)_request.GetResponse())
{
    using (System.IO.Stream _dataStream = _response.GetResponseStream())
    {
        using (System.IO.StreamReader _streamReader = new System.IO.StreamReader(_dataStream))
        {
            string _responseFromServer = _streamReader.ReadToEnd();
        }
    }
}

最后我尝试了这个方法,它有效:
StringBuilder _stringBuilder = new StringBuilder();
using (var _response = (System.Net.HttpWebResponse)_request.GetResponse())
{
    using (System.IO.Stream _dataStream = _response.GetResponseStream())
    {
        using (System.IO.StreamReader _streamReader = new System.IO.StreamReader(_dataStream))
        {
            while (!streamReader.EndOfStream)
            {
                char[] _buffer = new char[4096];
                _streamReader.ReadBlock(_buffer, 0, _buffer.Length);
                var _bufferString = new String(_buffer);
                _stringBuilder.Append(_bufferString);
            }
        }
    }
}

但是当它到达这里的下一行时,它会因为OutOfMemoryException错误而崩溃:

var _results = Newtonsoft.Json.JsonConvert.DeserializeObject<List<MyObject>>(_stringBuilder.ToString());

它不喜欢ToString()方法。

甚至连简单的一行代码也会崩溃。

string _convertedString = _stringBuilder.ToString();

完整的错误信息为:

类型为'System.OutOfMemoryException'的异常在mscorlib.dll中发生,但未在用户代码中处理

机器运行64位的Windows系统,内存大小为16GB。
所以,我的选择是什么?
我想要的只是从(非常大的)JSON字符串中获取一个>。

1
请查看此问题的答案,http://stackoverflow.com/questions/27315521/system-outofmemoryexception-with-json-net-with-listobject,一次反序列化一个对象。 - dbugger
2
OutOfMemoryException 可以因各种原因而发生,例如耗尽缓冲区、GDI 对象等。异常的调用堆栈将告诉您发生了什么,但真正的罪魁祸首是低效率的代码。为什么你要使用 StringBuilder 而不是通过 StreamReader 读取数据呢?你所做的并不比 streamReader.ReadToEnd() 更好(或更高效)。 - Panagiotis Kanavos
@PanagiotisKanavos - 这些只是我尝试让它工作的各种方法,只是为了展示过程。如果有一种方法可以在没有内存错误的情况下将StreamReader中的值传递到Deserializer中,那我会很乐意知道的。 - DeclanMcD
如果您创建一个“最小化、完整和可验证的示例”,那么我们更容易看到问题所在。 - Orkun Bekar
@dbugger - 我没看到那个。从来不知道你可以逐个反序列化一个项目。我会试一下看看是否有效。谢谢。 - DeclanMcD
1个回答

8
你的代码本质上模拟了 StreamReader.ReadToEnd 的功能,在读取大型响应时至少需要4倍的内存(字符串响应本身的内存,StringBuilder的内部缓冲区,所有中间临时字符串的大小和最终字符串的大小)。
您可以通过使用JsonTextReader直接从流进行反序列化来避免这种情况。从文档示例复制:
using (var json= new JsonTextReader(streamReader))
{
    JsonSerializer serializer = new JsonSerializer();
    return (List<MyObject>)serializer.Deserialize(json, typeof(List<MyObject>));
}

O


太完美了!第一次就成功了——只需要对你的代码进行微小的更改——将“file”改为“jsonTextReader”。 - DeclanMcD

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