2012-04-13 54 views
3

我的问题是,是否可以确定引用类型的序列化大小(以字节为单位)。确定.NET类型的序列化大小和非托管内存效率

继承人的情况:

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

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

我序列化每个项目一个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还是使用其他标准化的最佳实践?例如,如果BinaryFormatter的序列化说是一个通用列表<>,那么BinaryFormatter是如何划分对象的?

+1

这很危险;如果串行数据本身出现值为13的字节会怎么样?国际海事组织你应**前缀长度使用类似固定长度(通常4或8字节)网络字节顺序编码,或“varint”编码,如果你感到异国情调 – 2012-04-13 21:23:25

+0

@MarcGravell,谢谢..我使用回车作为临时解决方案。前缀长度的想法很好,我想用它。 – 2012-04-13 21:36:05

+0

根本没有必要做长度分隔。 BinaryFormatter知道它何时对一个完整的机箱进行反序列化。 – 2012-04-13 21:42:29

回答

3

事先没有确定序列化长度的非常好的方法。对于BinaryFormatter的协议规范,请访问: http://msdn.microsoft.com/en-us/library/cc236844(v=prot.10).aspx

我的麻烦节省您的阅读它为您的目的:

  1. 它的建成是一个可扩展的格式。这允许您稍后添加字段,并仍然保持与先前实现的一些兼容性。出于您的目的,这意味着序列化表单的长度不是固定的。
  2. 这是非常脆弱的。二进制格式实际上编码了其中的字段的名称。如果你重命名一个字段,序列化表单的长度将会改变。
  3. 二进制格式实际上包含了序列化编码和对象数据之间的多对一关系。同一个对象可以用许多不同的方式进行编码,输出的字节数不同(我不会理解为什么会这样写)。

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

最后,我可以告诉你,额外的内存拷贝并不是当前实现中效率低下的主要原因。您从BinaryFormatter的反射使用中获得的效率要低得多,并且它在序列化输出中对字段名进行编码。

如果效率是最重要的,那么我会建议编写一些自定义代码,用“普通旧数据”格式对结构内容进行编码。那么你就可以控制写入的数量和方式。

+0

谢谢 - 您是否知道任何现有的“普通旧数据”格式化程序,我都可以通过这些格式化程序来节省重新发明轮子的时间?我想最终我会推出自己的,但是暂时我只是在做一个概念验证。 – 2012-04-13 21:37:26

+0

我重写了其中一段,“如果你想要一个简单的方法......”这应该会给你一个更好的做事方式。至于POD格式化器,它们往往是一次性的。查看谷歌的协议缓冲区作为一个选项,但他们有自己的一套优点和缺点。 – 2012-04-13 21:40:19

4

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

将每个块的大小以字节为单位前缀,并以块的形式读取。

+1

嘿......我的想法确切。 – 2012-04-13 21:26:01

2

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

我拉起从注释中的一些信息,因为它是令人惊讶的还重要:

的CLR有元数据设施,让固定结构或类的本机布局。在C#中,这只适用于结构。但是类也可以这样使用。

如果您指定了SequentialLayout,则可以将受管理类型的位传输为字节。 http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.structlayoutattribute.aspx这个设施不是很有名,但它存在,被指定和支持。 Quote:“类布局属性(AutoLayout,SequentialLayout和ExplicitLayout)定义了类实例的字段在内存中的布局方式。”

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

+0

我想象一个引用类型不能有一个'本地大小'(一旦序列化,无论如何),对不对?这是我不确定的。 – 2012-04-13 21:29:18

+0

CLR具有用于固定结构或类的原生布局的元数据工具。在C#中,我认为这只适用于结构。但是类也可以这样使用。 – usr 2012-04-13 21:51:46

+0

当由BinaryFormatter序列化时,对象的内存大小与其大小无关。 – 2012-04-13 21:53:53