确定 .NET 类型的序列化大小和非托管内存效率

本文关键字:内存 效率 类型 NET 序列化 确定 | 更新日期: 2023-09-27 18:30:59

我的问题是是否可以确定引用类型的序列化大小(以字节为单位)。

情况如下:

我正在使用 BinaryFormatter 类来序列化基本的 .NET 类型,例如:

[Serializable]
public class Foo
{
    public string Foo1 { get; set; }
    public string Foo2 { get; set; } 
}

我将每个项目序列化为一个 byte[],然后将该段添加到现有 byte[] 的末尾,并在每个段的末尾添加回车符以分隔对象。

为了反序列化,我使用Marshal.ReadByte(),如下所示:

List<byte> buffer = new List<byte>();
for (int i = 0; i < MapSize; i++)
{
    byte b = Marshal.ReadByte(readPtr , i); 
    if (b != delim)  // read until encounter a carriage return 
        buffer.Add(b);
    else
        break;
}
readPtr = readPtr + buffer.Count + 1; // incrementing the pointer for the next object
return buffer.ToArray(); 

我相信使用 Marshal.Copy() 会更有效,但我需要提前知道序列化字节段的长度。有没有办法从正在序列化的类型中可靠地计算它,或者我可以使用的整体更有效的方法?

此外,最终使用回车并不可靠。所以我想知道是否有一种更标准的方法来分隔对象,无论是通过自定义我的 BinaryFormatter 还是使用其他一些标准化的最佳实践?例如,如果二进制格式化程序的序列化,是否有一种特定的方式来分隔对象,例如通用列表<>?

确定 .NET 类型的序列化大小和非托管内存效率

没有一个

非常好的方法来事先确定序列化长度。BinaryFormatter协议的规范可在此处获得:http://msdn.microsoft.com/en-us/library/cc236844(v=prot.10).aspx

为了您的目的,我将为您省去阅读它的麻烦:

  1. 它被构建为可扩展的格式。这允许您稍后添加字段,并且仍然保持与早期实现的一些兼容性。就您的目的而言,这意味着序列化表单的长度在时间上不是固定的。
  2. 它非常脆弱。二进制格式实际上对其中字段的名称进行编码。如果重命名字段,序列化窗体的长度将更改。
  3. 二进制格式实际上包含序列化编码和对象数据之间的多对一关系。同一个对象可能会以多种不同的方式进行编码,输出具有许多不同的字节计数(我不会讨论为什么这样写)。

如果你想要一种简单的方法来做事,只需创建一个包含所有对象的数组并序列化该单个数组。这解决了您的大部分问题。分隔不同对象的所有问题都由二进制格式化程序处理。您不会有过多的内存复制。最终输出将更加紧凑,因为二进制格式化程序每次调用只需指定一次字段名称。

最后,我可以告诉您,额外的内存副本并不是当前实现中效率低下的主要来源。由于 BinaryFormatter 使用反射,以及它在序列化输出中对字段名称进行编码的事实,效率会大大降低。

如果效率是最重要的,那么我建议编写一些自定义代码,以"纯旧数据"格式对结构的内容进行编码。然后,您将可以控制编写多少以及如何编写。

使用字节作为二进制序列化数据的分隔符是一个糟糕的想法 - 13 是完全有效的值,可以成为序列化数据的一部分,而不仅仅是您的"分隔符"。

改为为每个块添加前缀,以字节为单位的大小,并以块为单位读取它。

您可以使用 Marshal.SizeOf 来获取结构的本机大小。这仅适用于结构,我建议您设置 StructLayout 属性。

我会从评论中提取一些信息,因为它令人惊讶但很重要:

CLR 具有元数据工具,用于修复结构或类的本机布局。在 C# 中,这仅适用于结构。但是类也可以这样使用。

如果指定了 SequentialLayout,则可以将托管类型位为字节。 http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.structlayoutattribute.aspx 此功能并不为人所知,但它存在、指定并受支持。引用:"类布局属性(AutoLayout,SequentialLayout和ExplicitLayout)定义了类实例的字段在内存中的布局方式。

查看 System.Reflection.TypeAttributes 枚举。它还定义了其他 CLR 级别的属性。C# 不提供对它们的访问,但 ilasm.exe 可以访问它们。

我可以从 https://bytes.com/topic/c-sharp/answers/238927-object-size-memory 中找到使用此代码根本不序列化的原因

var m = new System.IO.MemoryStream();
var b = new
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
b.Serialize(m, Obj);
var size = Convert.ToDouble(m.Length);