c# - Protobuf-net 字段的惰性流式反序列化

标签 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);

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

我如何记住 FatProperty2 所在的流位置开始,我如何从随机索引中读取它?

注意:参数TFatArray2<T>可以是标有[ProtoContract]的其他类型,不仅仅是原语。也可能有多个 FatProperty2<T> 类型的属性在对象图中的不同深度。

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

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

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

问候, 尤利安

最佳答案

这个问题在很大程度上取决于实际 模型 - 这不是图书馆专门针对方便的场景。我怀疑您最好的选择是使用 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;
}

最后,请注意,如果这是一个大数组,您可能希望使用“打包”数组(请参阅 protobuf 文档,但这基本上存储它们时没有每个项目的标题)。这会更有效,但请注意,它需要稍微不同的读取代码。您可以通过添加 IsPacked = true 来启用压缩数组在 [ProtoMember(...)] 上对于那个数组。

关于c# - Protobuf-net 字段的惰性流式反序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25951775/

相关文章:

c# - 如何在.net c# 中的数据库对象中应用事务回滚

c# - 如何使用 protoBuf 进行结构序列化

c# - 从另一个项目添加 ApiAuthorizationDbContext 迁移 - EF Core

c# - 接口(interface)无法声明类型问题 C#

c# - 将 foreach 语句重写为 linq

C# PCL HMACSHAX 与 BouncyCaSTLe-PCL

c# - 为什么protobuf-net反序列化对象不是 'lean'

protobuf-net - 在没有通用类的情况下调用 PrepareSerializer

c# - "Type is not expected, and no contract can be inferred: Leap.Vector"错误。使用 Protobuf-net 序列化器

c# - 如何让protobuf-net和protostuff相互支持.Net和Java中的继承类?