美文网首页Android开发
FlatBuffers反序列化过程

FlatBuffers反序列化过程

作者: searchworld | 来源:发表于2017-08-29 21:43 被阅读748次

    FlatBuffers简介
    FlatBuffers Schema解析
    FlatBuffers序列化过程
    FlatBuffers反序列化过程

    上一篇详细讲解了FlatBuffers的序列化过程,现在来讲解其逆过程:反序列化。
    使用的代码仍然是SampleBinary.java,简单修改了下最后的输出。

    class SampleBinary {
      // Example how to use FlatBuffers to create and read binary buffers.
      public static void main(String[] args) {
        FlatBufferBuilder builder = new FlatBufferBuilder(0);
    
        // Create some weapons for our Monster ('Sword' and 'Axe').
        int weaponOneName = builder.createString("Sword");
        short weaponOneDamage = 3;
        int weaponTwoName = builder.createString("Axe");
        short weaponTwoDamage = 5;
    
        // Use the `createWeapon()` helper function to create the weapons, since we set every field.
        int[] weaps = new int[2];
        weaps[0] = Weapon.createWeapon(builder, weaponOneName, weaponOneDamage);
        weaps[1] = Weapon.createWeapon(builder, weaponTwoName, weaponTwoDamage);
    
        // Serialize the FlatBuffer data.
        int name = builder.createString("Orc");
        byte[] treasure = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        int inv = Monster.createInventoryVector(builder, treasure);
        int weapons = Monster.createWeaponsVector(builder, weaps);
        int pos = Vec3.createVec3(builder, 1.0f, 2.0f, 3.0f);
    
        Monster.startMonster(builder);
        Monster.addPos(builder, pos);
        Monster.addName(builder, name);
        Monster.addColor(builder, Color.Red);
        Monster.addHp(builder, (short)300);
        Monster.addInventory(builder, inv);
        Monster.addWeapons(builder, weapons);
        Monster.addEquippedType(builder, Equipment.Weapon);
        Monster.addEquipped(builder, weaps[1]);
        int orc = Monster.endMonster(builder);
    
        builder.finish(orc); // You could also call `Monster.finishMonsterBuffer(builder, orc);`.
    
        // We now have a FlatBuffer that can be stored on disk or sent over a network.
    
        // ...Code to store to disk or send over a network goes here...
    
        // Instead, we are going to access it right away, as if we just received it.
    
        ByteBuffer buf = builder.dataBuffer();
    
        // Get access to the root:
        Monster monster = Monster.getRootAsMonster(buf);
        System.out.println(monster.hp());
        System.out.println(monster.mana());
        System.out.println(monster.name());
        System.out.println(monster.pos());
        System.out.println(monster.inventory(1));
        System.out.println(monster.weapons(0).name());
        System.out.println(monster.weapons(0).damage());
      }
    }
    

    内存结构:

    8            24                                                           40                                                                                                     84                                                                       112                       124                              140                  148                          160              168                      180                  188                            199                            
    |            |                                                            |                                                                                                       |                                                                        |                         |                                |                    |                            |                |                        |                    |                              |                                         
    192         186                                                          160                                                                                                     116                                                                      88                        76                                60                   52                           40               32                       20                   12                             1                                    
    |          | |                                                          | |                                                                                                    |  |                                                                     |  |                       | |                              | |                 |  |                          | |              | |                      | |                  | |                              |                                        
     root_table                      Monster vtable                                                                  Monster                                                                                               path                                        weapons                        inv                        name                    axe                 Weapon vtable           sword                weaponTwoName             weaponOneName
    |          | |                                                          | |                                                                                                    |  |                                                                     |  |                       | |                              | |                 |  |                          | |              | |                      | |                  | |                              | 
    32 0 0 0 0 0 26 0 44 0 32 0 0 0 24 0 28 0 0 0 20 0 27 0 16 0 15 0 8 0 4 0 26 0 0 0 40 0 0 0 100 0 0 0 0 0 0 1 56 0 0 0 64 0 0 0 -12 1 0 0 72 0 0 0 0 0 -128 63 0 0 0 64 0 0 64 64 2 0 0 0 0 0 -128 64 0 0 -96 64 0 0 -64 64 0 0 -128 63 0 0 0 64 0 0 64 64 2 0 0 0 52 0 0 0 28 0 0 0 10 0 0 0 0 1 2 3 4 5 6 7 8 9 0 0 3 0 0 0 79 114 99 0 -12 -1 -1 -1 0 0 5 0 24 0 0 0 8 0 12 0 8 0 6 0 8 0 0 0 0 0 3 0 12 0 0 0 3 0 0 0 65 120 101 0 5 0 0 0 83 119 111 114 100 0 0 0 
    
    

    代码很简单,首先获取一个monster实例,然后就可以调用相应的方法获取值。获取值的逻辑也很简单:首先获取根据字段的vtable_offset从vtable中获取到offset,然后到对应的对象内存中读取对应的值。如果是非引用类型则直接获取值;如果是引用类型(string/vector/table)则从获取到的值引用的位置,需要再进行转化。

    1. getRootAsMonster获取root type

    Monster monster = Monster.getRootAsMonster(buffer);
      |
      public static Monster getRootAsMonster(ByteBuffer _bb, Monster obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
        |
        public Monster __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
          |
          public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
    

    getRootAsMonster这个方法只是简单的进行标记工作:1、标记root table的起始位置(_bb.position()保存的是到root table开始位置的offset);2、标记使用的ByteBuffer。
    序列化过程数据是从ByteBuffer的高位往低位写,反序列化的时候刚好相反,从低位往高位读。

    2. 从vtable获取offset,这里使用的是Table类的__offset方法

      /**
       * Look up a field in the vtable.
       *
       * @param vtable_offset An `int` offset to the vtable in the Table's ByteBuffer.
       * @return Returns an offset into the object, or `0` if the field is not present.
       */
      protected int __offset(int vtable_offset) {
        // 获取vtable的开始位置
        int vtable = bb_pos - bb.getInt(bb_pos);
        // bb.getShort(vtable) 获取vtable的大小,bb.getShort(vtable + vtable_offset)获取vtable_offset的字段对应于的值,如果返回0则使用默认值。
        return vtable_offset < bb.getShort(vtable) ? bb.getShort(vtable + vtable_offset) : 0;
      }
    

    3. 获取Primitive类型

    分两种情况:

    • 获取非默认值
    monster.hp()
      |
      public short hp() { int o = __offset(8); return o != 0 ? bb.getShort(o + bb_pos) : 100; }
    

    这里返回的__offset(8)为24,o + bb_pos为64,因此是获取offset为164的值,调用bb.getShort(o + bb_pos)得到-12 1,即500。

    • 获取默认值
    monster.mana()
      |
      public short mana() { int o = __offset(6); return o != 0 ? bb.getShort(o + bb_pos) : 150; }
    

    __offset(6)返回值为0,因此使用默认值150。

    4. 获取string

    monster.name()
      |
      public String name() { int o = __offset(10); return o != 0 ? __string(o + bb_pos) : null; }
        |
      /**
       * Create a Java `String` from UTF-8 data stored inside the FlatBuffer.
       *
       * This allocates a new string and converts to wide chars upon each access,
       * which is not very efficient. Instead, each FlatBuffer string also comes with an
       * accessor based on __vector_as_bytebuffer below, which is much more efficient,
       * assuming your Java program can handle UTF-8 data directly.
       *
       * @param offset An `int` index into the Table's ByteBuffer.
       * @return Returns a `String` from the data stored inside the FlatBuffer at `offset`.
       */
      protected String __string(int offset) {
        CharsetDecoder decoder = UTF8_DECODER.get();
        decoder.reset();
        
        // bb.getInt(offset)获取相对当前位置的偏移值,加上当前位置才是真正的偏移值。
        offset += bb.getInt(offset);
        ByteBuffer src = bb.duplicate().order(ByteOrder.LITTLE_ENDIAN);
        // string的第一个字段存储的是string的长度
        int length = src.getInt(offset);
        src.position(offset + SIZEOF_INT);
        src.limit(offset + SIZEOF_INT + length);
    
        int required = (int)((float)length * decoder.maxCharsPerByte());
        CharBuffer dst = CHAR_BUFFER.get();
        if (dst == null || dst.capacity() < required) {
          dst = CharBuffer.allocate(required);
          CHAR_BUFFER.set(dst);
        }
    
        dst.clear();
    
        try {
          CoderResult cr = decoder.decode(src, dst, true);
          if (!cr.isUnderflow()) {
            cr.throwException();
          }
        } catch (CharacterCodingException x) {
          throw new Error(x);
        }
    
        return dst.flip().toString();
      }
    

    string在table中存储的是引用值,即string开始的位置与存储引用值位置的距离(注意,这里存储的不是引用对象真正的offset,而是相对这个存储位置的offset,两者相加才是真正的偏移值。这是由addOffset方法决定的。)。
    __offset返回28,__string输入值为68,bb.getInt(68);得到72,offset += bb.getInt(offset);得到140,即为string的offset。

    5. 获取struct

    monster.pos().x()
      |
      public Vec3 pos() { return pos(new Vec3()); }
        |
        public Vec3 pos(Vec3 obj) { int o = __offset(4); return o != 0 ? obj.__assign(o + bb_pos, bb) : null; }
          |
          public Vec3 __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
            |
            public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
              |
              public float x() { return bb.getFloat(bb_pos + 0); }
    

    这里类似getRootAsMonster,最后只是标记下Vec3对象使用的ByteBuffer和开始的位置,最后直接从开始位置按字节读取即可。
    __offset(4)返回32,o + bb_pos返回72,刚好是pos在内存中的位置。

    6. 获取vector

    monster.inventory(1)
      |
      public int inventory(int j) { int o = __offset(14); return o != 0 ? bb.get(__vector(o) + j * 1) & 0xFF : 0; }
        |
         /**
          * Get the start data of a vector.
          *
          * @param offset An `int` index into the Table's ByteBuffer.
          * @return Returns the start of the vector data whose offset is stored at `offset`.
          */
          protected int __vector(int offset) {
            offset += bb_pos;
            return offset + bb.getInt(offset) + SIZEOF_INT;  // data starts after the length
          }
    

    获取vector类似string,主要逻辑在__vector(int offset)中,返回真正的偏移量,然后就可以按字节读取。

    7. 获取table

    获取table的逻辑也类似getRootAsMonster,转化offset后使用assign标记table的中的ByteBuffer和bb_pos,然后就可以使用上面的方法获取值。

    monster.weapons(0)
      |
      public Weapon weapons(int j) { return weapons(new Weapon(), j); }
        |
        public Weapon weapons(Weapon obj, int j) { int o = __offset(18); return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; }
    

    总结

    到这里FlatBuffers的内容基本结束。FlatBuffers给出了一种序列化和反序列的新的视角,在保持内存和速度的高效性的同时原理也很简单。虽然没有像Protocol Buffer那么有名,但是也有不少项目在使用。刚刚成为Apache顶级项目的Arrow就是使用FlatBuffers作为schema序列化存储格式的。但是FlatBuffers也有一个比较大的缺点,其生成的代码风格不太符合正常的调用习惯,这点从上面的代码中也可以看出来:构造的时候需要先构造string/vector/table,然后才开始构造root type,不能嵌套。当然这种风格的争议更多的使用习惯的问题,并不会影响到功能,因此对于大部分用户来说是可以忽略的。

    相关文章

      网友评论

        本文标题:FlatBuffers反序列化过程

        本文链接:https://www.haomeiwen.com/subject/jltadxtx.html