Protobuf-net懒惰流式反序列化字段

时间:2014-09-20 18:03:10

标签: c# protobuf-net

总体目标:在反序列化时跳过非常长的字段,并且在访问字段时直接从流中读取元素而不加载整个字段。

示例类正在序列化/反序列化的对象是FatPropertyClass

[ProtoContract]
public class FatPropertyClass
{
    [ProtoMember(1)]
    private int smallProperty;

    [ProtoMember(2)]
    private FatArray2<int> fatProperty;

    [ProtoMember(3)]
    private int[] array;

    public FatPropertyClass()
    {

    }

    public FatPropertyClass(int sp, int[] fp)
    {
        smallProperty = sp;
        fatProperty = new FatArray<int>(fp);
    }

    public int SmallProperty
    {
        get { return smallProperty; }
        set { smallProperty = value; }
    }

    public FatArray<int> FatProperty
    {
        get { return fatProperty; }
        set { fatProperty = value; }
    }

    public int[] Array
    {
        get { return array; }
        set { array = value; }
    }
}


[ProtoContract]
public class FatArray2<T>
{
    [ProtoMember(1, DataFormat = DataFormat.FixedSize)]
    private T[] array;
    private Stream sourceStream;
    private long position;

    public FatArray2()
    {
    }

    public FatArray2(T[] array)
    {
        this.array = new T[array.Length];
        Array.Copy(array, this.array, array.Length);
    }


    [ProtoBeforeDeserialization]
    private void BeforeDeserialize(SerializationContext context)
    {
        position = ((Stream)context.Context).Position;
    }

    public T this[int index]
    {
        get
        {
            // logic to get the relevant index from the stream.
            return default(T);
        }
        set
        {
            // only relevant when full array is available for example.
        }
    }
}

我可以这样反序列化:FatPropertyClass d = model.Deserialize(fileStream, null, typeof(FatPropertyClass), new SerializationContext() {Context = fileStream}) as FatPropertyClass;其中model可以是:

    RuntimeTypeModel model = RuntimeTypeModel.Create();
    MetaType mt = model.Add(typeof(FatPropertyClass), false);
    mt.AddField(1, "smallProperty");
    mt.AddField(2, "fatProperty");
    mt.AddField(3, "array");
    MetaType mtFat = model.Add(typeof(FatArray<int>), false);

这将跳过arrayFatArray<T>的反序列化。但是,我需要稍后从该数组中读取随机元素。我尝试过的一件事是在BeforeDeserialize(SerializationContext context) FatArray2<T>方法中记住反序列化之前的流位置。如上面的代码所示:position = ((Stream)context.Context).Position;。然而,这似乎始终是流的结束。

我怎样才能记住FatProperty2开始的流位置以及如何从随机索引中读取?

注意T中的参数FatArray2<T>可以是标有[ProtoContract]的其他类型,而不仅仅是基元。此外,在对象图中的不同深度处可能存在类型FatProperty2<T>的多个属性。

方法2 :在包含对象的序列化后序列化字段FatProperty2<T>。因此,使用长度前缀序列化FatPropertyClass,然后使用长度前缀序列化它包含的所有胖数组。使用属性标记所有这些fat数组属性,在反序列化时,我们可以记住每个属性的流位置。

然后问题是我们如何从中读取原语?这适用于使用T item = Serializer.DeserializeItems<T>(sourceStream, PrefixStyle.Base128, Serializer.ListItemTag).Skip(index).Take(1).ToArray();获取索引index项的类。但这对于原语是如何工作的呢?原始数组似乎无法使用DeserializeItems进行反序列化。

使用LINQ DeserializeItems就好吗?它是否按照我的假设执行(内部跳过流到正确的元素 - 最坏的情况是读取每个长度前缀并跳过它)?

此致 尤利安

1 个答案:

答案 0 :(得分:1)

这个问题在实际模型上非常依赖 - 这不是图书馆专门针对方便的目标。我怀疑你最好的选择是使用ProtoReader手动编写阅读器。请注意,如果最外层对象是List<SomeType>或类似对象,则在读取所选项目时 一些技巧,但内部对象通常只是简单读取或跳过。

通过ProtoReader从文档的根目录重新开始,您可以非常有效地寻找第n个项目。如果你愿意,我可以稍后做一个具体的例子(除非你确定它确实有用,否则我没有跳过)。作为参考,流的位置在这里没有用的原因是:库积极地过度读取和缓冲数据,除非您明确告诉它限制其长度。这是因为像“varint”这样的数据很难在没有大量缓冲的情况下有效地读取,因为它最终会对ReadByte()进行大量的单独调用,而不仅仅是使用本地缓冲区。


这是直接从阅读器读取子属性的第n个数组项的完全未经测试的版本;请注意,一个接一个地调用这么多次是低效的,但是应该很明显如何将其更改为读取范围的连续值等等:

static int? ReadNthArrayItem(Stream source, int index, int maxLen)
{
    using (var reader = new ProtoReader(source, null, null, maxLen))
    {
        int field, count = 0;
        while ((field = reader.ReadFieldHeader()) > 0)
        {
            switch (field)
            {
                case 2: // fat property; a sub object
                    var tok = ProtoReader.StartSubItem(reader);
                    while ((field = reader.ReadFieldHeader()) > 0)
                    {
                        switch (field)
                        {
                            case 1: // the array field
                                if(count++ == index)
                                    return reader.ReadInt32();
                                reader.SkipField();
                                break;
                            default:
                                reader.SkipField();
                                break;
                        }
                    }
                    ProtoReader.EndSubItem(tok, reader);
                    break;
                default:
                    reader.SkipField();
                    break;
            }
        }
    }
    return null;
}

最后,请注意,如果这是一个大型数组,您可能需要使用“packed”数组(请参阅protobuf文档,但这基本上存储它们而没有每个项目的标题)。这会更有效率,但请注意它需要稍微不同的阅读代码。您可以通过将IsPacked = true添加到该阵列的[ProtoMember(...)]来启用打包数组。