我遇到了在.NET中序列化大量对象的问题。对象图非常大,使用了一些新的数据集,所以我得到了:
System.Runtime.Serialization.SerializationException
"The internal array cannot expand to greater than Int32.MaxValue elements."
还有其他人达到此限制吗?你是怎么解决的?
如果可能的话,我仍然可以使用内置的序列化机制,那将是很好的,但似乎只需要自己滚动(并保持与现有数据文件的向后兼容性)
对象全部为POCO,并使用BinaryFormatter
进行序列化。被序列化的每个对象都实现ISerializable
以有选择地序列化其成员(其中一些在加载期间重新计算)。
对于MS(details here)来说,这似乎是一个悬而未决的问题,但它已被解决为Wont Fix。细节是(来自链接):
对象的二进制序列化失败 图表超过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};
然而,如果这样会很好 序列化适用于任何 对象图的合理大小。
答案 0 :(得分: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));
}
}
}
答案 1 :(得分:1)
您是否考虑过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%的方法可以解决这个问题 - 您只需确保从正在存储的对象图中删除任何周期,以存储对象树相反。
经常使用的一种技术是存储引用对象的名称(或 id )而不是实际引用,将名称解析回对象负荷。
答案 2 :(得分:1)
根据数据的结构,您可以序列化/反序列化大对象图的子图吗?如果数据可以以某种方式进行分区,那么您可以放弃它,只创建一小部分序列化数据。
答案 3 :(得分:1)
.NET Core 2.1已解决该问题。我已要求将解决方案反向移植到.NET Framework 4.8:
https://github.com/Microsoft/dotnet-framework-early-access/issues/46。
如果您认为该问题已得到解决,可以发表评论,这对您也很重要。 .NET Core中的修复程序是为BinaryFormatter重用Dictionary中存在的素数生成器。
如果序列化了很多对象,并且您不想等待40分钟来读回它们,请确保将其添加到您的App中。
<?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>
启用BinaryFormatter反序列化修复程序,该修复程序最终在.NET 4.7.2中提供。有关这两个问题的更多信息,请参见:
https://aloiskraus.wordpress.com/2017/04/23/the-definitive-serialization-performance-guide/
答案 4 :(得分:0)
我猜...一次序列化较少的对象?
2个主要问题:
序列化需要某些考虑数据量是什么;例如,一些序列化框架支持对象和序列化数据的流式传输,而不是依赖于完整的对象图或临时存储。
另一个选择是序列化同类数据集而不是完整图表 - 即将所有“客户”分别序列化为“订单”;这通常会减少数量,但代价是更复杂。
那么:这里的情景是什么?
答案 5 :(得分:0)
听起来你遇到了框架中的内部限制。您可以使用BinaryReader / Writer或DataContractSerializer或其他任何内容编写自己的序列化,但我知道这并不理想。
答案 6 :(得分:0)
老兄,你已经到了.net的尽头了!
我没有达到这个限制,但这里有一些指示:
使用[XmlIgnore]跳过一些对象 - 也许你不需要序列化所有东西
您可以手动使用序列化程序(即不使用属性,而是通过实现Serialize())并将模型划分为更多文件。
答案 7 :(得分:0)
您是否需要同时获取所有数据? 1300万个对象是一次可以处理的大量信息。
您可以实现分页机制并以较小的块获取数据。它可能会提高应用程序的响应速度,因为您不必等待所有这些对象完成序列化。
答案 8 :(得分:0)
大对象的二进制序列化
如果您使用BinaryFormater遇到此限制binaryformatter the internal array cannot expand to greater than int32.maxvalue elements
,请使用此代码段帮助自己
步骤1
安装nuget软件包:安装软件包Newtonsoft.Json.Bson-版本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);
}
}
}