在.NET中序列化大量对象时出现SerializationException

Wil*_*lka 21 .net serialization

我遇到了在.NET中序列化大量对象的问题.对象图非常大,使用了一些新的数据集,所以我得到:

System.Runtime.Serialization.SerializationException
"The internal array cannot expand to greater than Int32.MaxValue elements."
Run Code Online (Sandbox Code Playgroud)

还有其他人达到这个限制吗?你是怎么解决的?

如果可能的话我仍然可以使用内置的序列化机制会很好,但似乎只需要自己滚动(并保持与现有数据文件的向后兼容性)

对象都是POCO,并且正在使用它们进行序列化BinaryFormatter.被序列化的每个对象实现ISerializable选择性地序列化其成员(其中一些在加载期间重新计算).

对于MS来说,这看起来像是一个未解决的问题(详情请参见此处),但它已被解决为Wont Fix.细节是(来自链接):

对于具有超过~1320万个对象的对象图,二进制序列化失败.尝试这样做会导致ObjectIDGenerator.Rehash中出现异常,并引用一个引用Int32.MaxValue的误导性错误消息.

在检查SSCLI源代码中的ObjectIDGenerator.cs后,似乎可以通过在sizes数组中添加其他条目来处理更大的对象图.请参阅以下行:

// Table of prime numbers to use as hash table sizes. Each entry is the
// smallest prime number larger than twice the previous entry.
private static readonly int[] sizes = {5, 11, 29, 47, 97, 197, 397,
797, 1597, 3203, 6421, 12853, 25717, 51437, 102877, 205759, 
411527, 823117, 1646237, 3292489, 6584983};
Run Code Online (Sandbox Code Playgroud)

但是,如果序列化适用于任何合理大小的对象图,那将是很好的.

Blu*_*kMN 10

我尝试重现这个问题,但即使每个1300多万个对象只有2个字节,代码也需要永远运行.因此,我怀疑您不仅可以解决问题,还可以在自定义ISerialize实施中将数据打包得更好时显着提高性能.不要让序列化程序看到你的结构如此深入,而是在你的对象图形爆炸成数十万个数组或更多元素的位置切掉它(因为假设你有很多对象,它们很小)或者你无论如何都无法将它们留在记忆中).以此示例为例,它允许序列化程序查看类B和C,但手动管理类A的集合:

class Program
{
    static void Main(string[] args)
    {
        C c = new C(8, 2000000);
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        bf.Serialize(ms, c);
        ms.Seek(0, System.IO.SeekOrigin.Begin);
        for (int i = 0; i < 3; i++)
            for (int j = i; j < i + 3; j++)
                Console.WriteLine("{0}, {1}", c.all[i][j].b1, c.all[i][j].b2);
        Console.WriteLine("=====");
        c = null;
        c = (C)(bf.Deserialize(ms));
        for (int i = 0; i < 3; i++)
            for (int j = i; j < i + 3; j++)
                Console.WriteLine("{0}, {1}", c.all[i][j].b1, c.all[i][j].b2);
        Console.WriteLine("=====");
    }
}

class A
{
    byte dataByte1;
    byte dataByte2;
    public A(byte b1, byte b2)
    {
        dataByte1 = b1;
        dataByte2 = b2;
    }

    public UInt16 GetAllData()
    {
        return (UInt16)((dataByte1 << 8) | dataByte2);
    }

    public A(UInt16 allData)
    {
        dataByte1 = (byte)(allData >> 8);
        dataByte2 = (byte)(allData & 0xff);
    }

    public byte b1
    {
        get
        {
            return dataByte1;
        }
    }

    public byte b2
    {
        get
        {
            return dataByte2;
        }
    }
}

[Serializable()]
class B : System.Runtime.Serialization.ISerializable
{
    string name;
    List<A> myList;

    public B(int size)
    {
        myList = new List<A>(size);

        for (int i = 0; i < size; i++)
        {
            myList.Add(new A((byte)(i % 255), (byte)((i + 1) % 255)));
        }
        name = "List of " + size.ToString();
    }

    public A this[int index]
    {
        get
        {
            return myList[index];
        }
    }

    #region ISerializable Members

    public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
    {
        UInt16[] packed = new UInt16[myList.Count];
        info.AddValue("name", name);
        for (int i = 0; i < myList.Count; i++)
        {
            packed[i] = myList[i].GetAllData();
        }
        info.AddValue("packedData", packed);
    }

    protected B(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
    {
        name = info.GetString("name");
        UInt16[] packed = (UInt16[])(info.GetValue("packedData", typeof(UInt16[])));
        myList = new List<A>(packed.Length);
        for (int i = 0; i < packed.Length; i++)
            myList.Add(new A(packed[i]));
    }

    #endregion
}

[Serializable()]
class C
{
    public List<B> all;
    public C(int count, int size)
    {
        all = new List<B>(count);
        for (int i = 0; i < count; i++)
        {
            all.Add(new B(size));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Alo*_*aus 5

.NET Core 2.1 已修复该问题。我已请求将解决方案向后移植到 .NET Framework 4.8:

https://github.com/Microsoft/dotnet-framework-early-access/issues/46

如果您认为问题应该得到解决,您可以发表评论,表明这对您也很重要。.NET Core 中的修复方法是为 BinaryFormatter 重用 Dictionary 中存在的素数生成器。

如果您序列化了如此多的对象,并且您不想等待 40 分钟来读回它们,请确保将以下内容添加到您的 App.Config 中:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <!-- Use this switch to make BinaryFormatter fast with large object graphs starting with .NET 4.7.2 -->
      <AppContextSwitchOverrides value="Switch.System.Runtime.Serialization.UseNewMaxArraySize=true" />
  </runtime>
</configuration>
Run Code Online (Sandbox Code Playgroud)

启用 BinaryFormatter 反序列化修复,该修复最终随 .NET 4.7.2 一起发布。有关这两个问题的更多信息可以在这里找到:

https://aloiskraus.wordpress.com/2017/04/23/the-definitive-serialization-performance-guide/