[英]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.