【问题标题】:SerializationException when serializing lots of objects in .NET在 .NET 中序列化大量对象时出现 SerializationException
【发布时间】:2010-10-08 19:42:36
【问题描述】:

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

System.Runtime.Serialization.SerializationException
"The internal array cannot expand to greater than Int32.MaxValue elements."

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

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

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

这似乎是 MS (details here) 的未解决问题,但它已被解决为无法修复。详细信息(来自链接):

对象的二进制序列化失败 超过~1320万的图表 对象。这样做的尝试导致 一个例外 ObjectIDGenerator.Rehash 与 误导性错误消息引用 Int32.MaxValue.

经审查 SSCLI 中的 ObjectIDGenerator.cs 源代码,看起来更大 对象图可以由 将附加条目添加到 大小数组。请参阅以下几行:

// 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};

但是,如果 序列化适用于任何 对象图的合理大小。

【问题讨论】:

    标签: .net serialization


    【解决方案1】:

    我猜...一次序列化更少的对象?

    2 个主要问题:

    • 它们是什么对象?
      • POCO?
      • 数据表?
    • 它是什么类型的序列化?
      • xml?
        • XmlSerializer?
        • DataContractSerializer?
      • 二进制?
        • BinaryFormatter?
        • SoapFormatter?
      • 其他?
        • json?
        • 定制?

    序列化需要一些考虑数据量是多少;例如,一些序列化框架支持对象和序列化数据的流式传输,而不是依赖于完整的对象图或临时存储。

    另一种选择是序列化同质数据集而不是完整图 - 即将所有“客户”分别序列化和“订单”;这通常会减少体积,但会增加复杂性。

    那么:这里的场景是什么?

    【讨论】:

    • 我已将问题更新为(希望)涵盖您的 2 个问题。
    【解决方案2】:

    听起来您遇到了框架的内部限制。您可以使用BinaryReader/WriterDataContractSerializer 或其他任何方式编写自己的序列化,但我知道这并不理想。

    【讨论】:

      【解决方案3】:

      老兄,你已经到达 .net 的尽头了!

      我还没有达到这个限制,但这里有几个指针:

      1. 使用 [XmlIgnore] 跳过一些对象 - 也许您不需要序列化所有内容

      2. 您可以手动使用序列化程序(即不使用属性,而是通过实现 Serialize() )并将模型划分为更多文件。

      【讨论】:

        【解决方案4】:

        您是否考虑过 Int32.MaxValue 是 2,147,483,647 - 超过 2 十亿 的事实。

        您需要 16GB 的内存只是为了存储指针(假设是 64 位机器),更不用说对象本身了。在 32 位机器上只有一半,尽管将 8GB 的​​指针数据压缩到最大 3GB 左右的可用空间中会是一个好技巧。

        我强烈怀疑您的问题不是对象的数量,而是序列化框架正在进入某种无限循环,因为您的数据结构中有引用循环。

        考虑这个简单的类:

        public class Node
        {
            public string Name {get; set;}
            public IList<Node> Children {get;}
            public Node Parent {get; set;}
            ...
        }
        

        这个简单的类不能被序列化,因为 Parent 属性的存在意味着序列化会进入一个无限循环。

        由于您已经在实现 ISerializable,因此您已经完成了 75% 的解决方法 - 您只需要确保从正在存储的对象图中删除任何循环,以存储对象 取而代之。

        一种经常使用的技术是存储被引用对象的 name(或 id)而不是实际引用,将名称解析回对象加载。

        【讨论】:

        • 问题状态 BinaryFormatter;正确处理引用/递归。
        • Int32.MaxValue 消息具有误导性,我将在我的问题中添加更多细节以对此进行扩展。
        【解决方案5】:

        您需要同时获取所有数据吗? 1300 万 个对象是一次需要处理的大量信息。

        您可以实现分页机制并以较小的块获取数据。而且它可能会提高应用程序的响应能力,因为您不必等待所有这些对象完成序列化。

        【讨论】:

        • 它一次需要大部分数据(有些可能会被删除)。它是统计分析所必需的。我不关心这部分程序的内存使用情况(它在 64 位上运行,具有相当数量的 ram)。将东西交换到磁盘可能会使分析变得非常缓慢。
        【解决方案6】:

        根据数据的结构,也许您可​​以序列化/反序列化大对象图的子图?如果数据可以以某种方式进行分区,您就可以侥幸逃脱,只创建序列化数据的少量重复。

        【讨论】:

          【解决方案7】:

          我尝试重现该问题,但即使 13+ 百万个对象中的每一个都只有 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));
                  }
              }
          }
          

          【讨论】:

          • 像这样打包数据似乎是一个非常好的主意。我什至可以使用 MemoryStream 进行打包 - 所以很多代码不需要更改(它可以继续保存当前的方式)。也许只是为了让“流行”类将对象的数量保存到一个合理的数字。
          【解决方案8】:

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

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

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

          如果您有这么多对象序列化并且您不想等待 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>
          

          启用最终随 .NET 4.7.2 提供的 BinaryFormatter 反序列化修复。可以在此处找到有关这两个问题的更多信息:

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

          【讨论】:

            【解决方案9】:

            超大对象的二进制序列化

            如果您在使用 BinaryFormater 时遇到此限制binaryformatter the internal array cannot expand to greater than int32.maxvalue elements,请自行使用此代码 sn-p 第 1 步

            安装 nuget 包:Install-Package Newtonsoft.Json.Bson -Version 1.0.2

            using Newtonsoft.Json.Bson; //import require namesapace
            
            //Code snippet for serialization/deserialization
            
            public byte[] Serialize<T>(T obj)
            {
                using (var memoryStream = new MemoryStream())
                {
                    using (var writer = new BsonDataWriter(memoryStream))
                    {
                        var serializer = new JsonSerializer();
                        serializer.Serialize(writer, obj);
                    }
                    return memoryStream.ToArray();
                }
            }
            
            public T Deserialize<T>(byte[] data)
            {
                using (var memoryStream = new MemoryStream(data))
                {
                    using (var reader = new BsonDataReader(memoryStream))
                    {
                        var serializer = new JsonSerializer();
                        return serializer.Deserialize<T>(reader);
                    }
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-02-11
              • 2010-10-01
              • 1970-01-01
              • 2011-01-08
              • 1970-01-01
              相关资源
              最近更新 更多