簡體   English   中英

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

[英]Protobuf-net lazy streaming deserialization of fields

總體目標 :在反序列化時跳過一個很長的字段,並且在訪問字段時直接從流中讀取元素而不加載整個字段。

示例類被序列化/反序列化的對象是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);

這將跳過FatArray<T>array的反序列化。 但是,我需要稍后從該數組中讀取隨機元素。 我嘗試過的一件事是在FatArray2<T>BeforeDeserialize(SerializationContext context)方法中記住反BeforeDeserialize(SerializationContext context)之前的流位置。 如上面的代碼所示: position = ((Stream)context.Context).Position; 然而,這似乎始終是流的結束。

我怎么能記住FatProperty2開始的流位置以及如何從隨機索引中讀取它?

注意FatArray2<T>的參數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似乎無法對原語數組進行反序列DeserializeItems

使用LINQ的DeserializeItems就好了嗎? 它是否按照我的假設執行(內部跳過流到正確的元素 - 最壞的情況是讀取每個長度前綴並跳過它)?

此致,尤利安

這個問題在很大程度上取決於實際的模型 - 這不是圖書館專門為方便起見而設定的方案。 我懷疑你最好的選擇是使用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(...)]來啟用壓縮數組。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM