总体目标:在反序列化时跳过非常长的字段,并且在访问字段时直接从流中读取元素而不加载整个字段。
示例类正在序列化/反序列化的对象是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
开始的流位置以及如何从随机索引中读取?
注意: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
就好吗?它是否按照我的假设执行(内部跳过流到正确的元素 - 最坏的情况是读取每个长度前缀并跳过它)?
此致 尤利安
答案 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(...)]
来启用打包数组。