序列化为MemoryStream会导致OutOfmemoryException,但序列化为FileStream则不会。谁能告诉我为什么?

时间:2015-08-03 20:38:06

标签: c# json serialization json.net

我正在使用Newtonsoft Json.Net将对象序列化为json。当我尝试序列化为 MemoryStream 时,我不断遇到 OutOfMemoryException ,但在我序列化为 FileStream 时却没有。有人可以解释为什么会这样吗?这些是我用来序列化的两种方法。

引发OutOfMemoryException

        private static MemoryStream _serializeJson<T>(T obj)
    {
        try
        {
            var stream = new MemoryStream();
            var streamWriter = new StreamWriter(stream);
            var jsonWriter = new JsonTextWriter(streamWriter);
            var serializer = new JsonSerializer();
            serializer.ContractResolver = new CamelCasePropertyNamesContractResolver();
            serializer.Formatting = Formatting.Indented;
            serializer.Serialize(jsonWriter, obj);
            streamWriter.Flush();
            stream.Position = 0;
            return stream;
        }
        catch (Exception e)
        {
            //Logger.WriteError(e.ToString());
            Console.WriteLine(e.ToString());
            return null;
        }
    }

不会抛出OutOfMemoryException

    private static void _serializeJsonToFile<T>(T obj, string path)
    {
        try
        {
            using (FileStream fs = File.Open(path, FileMode.Create, FileAccess.ReadWrite))
            using (StreamWriter sw = new StreamWriter(fs))
            using (JsonWriter jw = new JsonTextWriter(sw))
            {
                jw.Formatting = Formatting.Indented;
                JsonSerializer serializer = new JsonSerializer();
                serializer.ContractResolver = new CamelCasePropertyNamesContractResolver();
                serializer.Serialize(jw, obj);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

P.S。有些人可能会问我为什么要返回一个流而不是简单地序列化到文件流。这是因为我想在一个类中保持序列化,在另一个类中保持文件处理,所以我稍后将内存流传递给另一个类中的WriteFile方法。

1 个答案:

答案 0 :(得分:2)

你正在获得OutOfMemoryExceptions,因为内存流对它的增长非常积极。每次需要重新调整大小时,它都会将内部缓冲区加倍。

//The code from MemoryStream http://referencesource.microsoft.com/mscorlib/system/io/memorystream.cs.html#1416df83d2368912
private bool EnsureCapacity(int value) {
    // Check for overflow
    if (value < 0)
        throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong"));
    if (value > _capacity) {
        int newCapacity = value;
        if (newCapacity < 256)
            newCapacity = 256;
        // We are ok with this overflowing since the next statement will deal
        // with the cases where _capacity*2 overflows.
        if (newCapacity < _capacity * 2)
            newCapacity = _capacity * 2;
        // We want to expand the array up to Array.MaxArrayLengthOneDimensional
        // And we want to give the user the value that they asked for
        if ((uint)(_capacity * 2) > Array.MaxByteArrayLength)
            newCapacity = value > Array.MaxByteArrayLength ? value : Array.MaxByteArrayLength;

        Capacity = newCapacity;
        return true;
    }
    return false;
}

使用17.8 MB的文件,这是使用35.6 MB字节数组的最坏情况。在调整大小过程中丢弃的旧字节数组也可能导致Memory Fragmentation取决于它们存活多长时间,这可以很容易地让程序在达到32位内存限制之前抛出OOM错误。

直接写入FileStream不需要在内存中创建任何大缓冲区,因此它占用的空间更少。

有一种方法可以将保存的逻辑与序列化分开,只需将流传递给函数,而不是在函数本身中创建它。

private static void _serializeJson<T>(T obj, Stream stream)
{
    try
    {
        using(var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true))
        using(var jsonWriter = new JsonTextWriter(streamWriter))
        {
            var serializer = new JsonSerializer();
            serializer.ContractResolver = new CamelCasePropertyNamesContractResolver();
            serializer.Formatting = Formatting.Indented;
            serializer.Serialize(jsonWriter, obj);
         }
    }
    catch (Exception e)
    {
        //Logger.WriteError(e.ToString());
        Console.WriteLine(e.ToString());
    }
}

我还处理了创建的StreamWriter,我使用的构造函数有一个leaveOpen标志,当你处理StreamWriter时,它会导致底层流不被关闭。