protobuf中 repeated[Ptr]Field的序列化

message Test1 {
  required int32 a = 1;
}

Test1 t1; t1.set_a(150); 序列化之后的结果是

08 96 01。 其中08 >>3 == 1是a的字段序号; 08的低3位(0)是类型varint(int32)

96 01 = 1001 0110  0000 0001
       → 000 0001  ++  001 0110 (drop the msb and reverse the groups of 7 bits)
       → 10010110
       → 2 + 4 + 16 + 128 = 150


message Test3 { required Test1 c = 3;}

Test3 t3; t3.mutable_c()->set_a(150);
序列化之后的结果是

1a 03 08 96 01
同上。
1a >>3 == 3是c的字段序号; 1a的低3位(2)类型是length-delimited(embeded message)。
第二个字节的03  表示后续 08 96 01的字节个数
08 96 01就是c序列化的结果。




从这里可以很明显的看出, 在序列化结构体时, 是不包含结构体本身的任何信息的。 那么对于repeated如何序列化?
pack? 从google看, 需要使用protobuf的cimplementation:protobuf-c, 且只对primitive有效。见http://code.google.com/p/protobuf-c/wiki/Examples

此前, 
message Test
{
      repeated int32 a = 1;
}

string s; ....OutputCodedStream ss(...s...);
repeatedPtrField tt;....//假设tt.size() == 3
for(int i= 0; i < tt.size(); i++)
     tt.SerializeToCodedStream(&ss);////多个Test对象这样序列化时, 在stream中是没有边界的, 再ParseFrom的时候就会出问题了。。。

之后从s 反序列化时, 只能得到一个Test对象, 且tt中3个Test中的a数组的值都放到了这个唯一的Test对象中。。。。!!!!


因此,我这里不得不使用较原始的方法:
template  bool RepeatedPtrSerialize(const google::protobuf::RepeatedPtrField &tmp, string &s)
{
//特别强调, 在下述语句中, 不能以任何方式访问s, 否则得到的结果就不正确。
    google::protobuf::io::StringOutputStream ss(&s);
    google::protobuf::io::CodedOutputStream codedOutput(&ss);
    for(int i = 0 ;i < tmp.size(); i++)
    {   
        codedOutput.WriteLittleEndian32(tmp.Get(i).ByteSize());
        tmp.Get(i).SerializeToCodedStream(&codedOutput);
    }   
        
    return true;
}

template  bool RepeatedPtrParseFrom(google::protobuf::RepeatedPtrField &tmp, const string &s)
{
  stringstream sss(s);
  google::protobuf::io::IstreamInputStream ssi(&sss);
  google::protobuf::io::CodedInputStream codedOutputi(&ssi);
  while(!codedOutputi.ExpectAtEnd())
  {
     unsigned int len = 0;
     string s;
     if(!codedOutputi.ReadLittleEndian32(&len)) break;
     if(!codedOutputi.ReadString(&s, len)) break;
     T *obj = tmp.Add();
     obj->ParseFromString(s);从这里再构造T。。。
  }

  return true;
}

https://developers.google.com/protocol-buffers/docs/encoding

序列化时, 按的形式存储; 多个字段的话顺序存储; 字段间无顺序关系。

key: 由字段顺序号 + 字段类型。 field_num << 3 | wire_type。  字段类型如下:

Type	Meaning	Used For
0	Varint	int32, int64, uint32, uint64, sint32, sint64, bool, enum
1	64-bit	fixed64, sfixed64, double
2	Length-delimited	string, bytes, embedded messages, packed(only support repeated primitive fields) repeated fields
3	Start group	groups (deprecated)
4	End group	groups (deprecated)
5	32-bit	fixed32, sfixed32, float

关于varint, 使用可变长度编码上述类型。

小于128的无符号, 用1个字节就行, 比uint32节约3个字节, 比uint64节约5个字节。

多个字节表示1个数时,前面的字节最高位都为1; 最后一个字节的最高位为0,表示是最后一个字节了。

但是对于负数,不太管用,因为最高位的符号位为1。 因此对于sint32, sint64使用ZigZag压缩。

以int32为例, ZigZag将区间[-2^31,   2^31 - 1] 映射为[0, 2^32 - 1]. 映射后的值 n 如果是偶数, 则原来的数就是n/2, 否则就是 -(n+1)/2。

Zigzag(n) = (n << 1) ^ (n >> 31),  n为sint32时

Zigzag(n) = (n << 1) ^ (n >> 63),  n为sint64时

下表是一个比较直观的映射表,这样映射后再进行编码的好处就是绝对值比较小的负数序列化后的结果占的Bytes数也会比较少。
Signed Original	Encoded As
0	0
-1	1
1	2
-2	3
2	4
-3	5
…	…
2147483647	4294967294
-2147483648	4294967295
length-delimited(wire_type=2)的编码方式:key+length+content, key的编码方式是统一的,length采用varints编码方式,content就是由length指定的长度的Bytes。

3)ProtoBuf编解码中字段顺序(Field order)的问题:

(a) 编码/解码与字段顺序无关,这一点由key-value机制就能保证

(b)对于未知的字段,编码的时候会把它写在序列化完的已知字段后面。



你可能感兴趣的:(protobuf中 repeated[Ptr]Field的序列化)