<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    上善若水
    In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
    posts - 146,comments - 147,trackbacks - 0

    基本類型編碼

    在前文有提到消息是一系列的基本類型以及其他消息類型的組合,因而基本類型是probobuf編碼實現的基礎,這些基本類型有:

    .proto Type

    Java Type

    C++ Type

    Wire Type

    double

    double

    double

    WIRETYPE_FIXED64(1)

    float

    float

    float

    WIRETYPE_FIXED32(5)

    int64

    long

    int64

    WIRETYPE_VARINT(0)

    int32

    int

    int32

    WIRETYPE_VARINT(0)

    uint64

    long

    unit64

    WIRETYPE_VARINT(0)

    uint32

    int

    unit32

    WIRETYPE_VARINT(0)

    sint64

    long

    int64

    WIRETYPE_VARINT(0)

    sint32

    int

    int32

    WIRETYPE_VARINT(0)

    fixed64

    long

    unit64

    WIRETYPE_FIXED64(1)

    fixed32

    int

    unit32

    WIRETYPE_FIXED32(5)

    sfixed64

    long

    int64

    WIRETYPE_FIXED64(1)

    sfixed32

    int

    int32

    WIRETYPE_FIXED32(5)

    bool

    boolean

    bool

    WIRETYPE_VARINT(0)

    string

    String

    string

    WIRETYPE_LENGTH_DELIMITED(2)

    bytes

    ByteString

    string

    WIRETYPE_LENGTH_DELIMITED(2)

    Java種對不同類型的選擇,其他的類型區別很明顯,主要在與int32uint32sint32fixed32中以及對應的64位版本的選擇,因為在Java中這些類型都用int(long)來表達,但是protobuf內部使用ZigZag編碼方式來處理多余的符號問題,但是在編譯生成的代碼中并沒有驗證邏輯,比如uint的字段不能傳入負數之類的。而從編碼效率上,對fixed32類型,如果字段值大于2^28,它的編碼效率比int32更加有效;而在負數編碼上sint32的效率比int32要高;uint32則用于字段值永遠是正整數的情況。

    在實現上,protobuf使用CodedOutputStream實現序列化邏輯、CodedInputStream實現反序列化邏輯,他們都包含write/read基本類型和Message類型的方法,write方法中同時包含fieldNumbervalue參數,在寫入時先寫入由fieldNumberWireType組成的tag值(添加這個WireType類型信息是為了在對無法識別的字段編碼時可以通過這個類型信息判斷使用那種方式解析這個未知字段,所以這幾種類型值即可),這個tag值是一個可變長int類型,所謂的可變長類型就是一個字節的最高位(msbmost significant bit)用1表示后一個字節屬于當前字段,而最高位0表示當前字段編碼結束。在寫入tag值后,再寫入字段值value,對不同的字段類型采用不同的編碼方式:
    1. int32/int64類型,如果值大于等于0,直接采用可變長編碼,否則,采用64位的可變長編碼,因而其編碼結果永遠是10個字節,所有說它int32/int64類型在編碼負數效率很低(然而這里我一直木有想明白對int32類型為什么需要做64位的符號擴展,不擴展,5個字節就可以了啊,而且對64位的負數也不需要用符號擴展,或者無法符號擴展,google上也沒有找到具體原因)。

    2. uint32/uint64類型,也采用變長編碼,不對負數做驗證。

    3. sint32/sint64類型,首先對該值做ZigZag編碼,以保留,然后將編碼后的值采用變長編碼。所謂ZigZag編碼即將負數轉換成正數,而所有正數都乘2,如0編碼成0-1編碼成11編碼成2-2編碼成3,以此類推,因而它對負數的編碼依然保持比較高的效率。

    4. fixed32/sfixed32/fixed64/sfixed64類型,直接將該值以小端模式的固定長度編碼。

    5. double類型,先將double轉換成long類型,然后以8個字節固定長度小端模式寫入。

    6. float類型,先將float類型轉換成int類型,然后以4個字節固定長度小端模式寫入。

    7. bool類型,寫01的一個字節。

    8. string類型,使用UTF-8編碼獲取字節數組,然后先用變長編碼寫入字節數組長度,然后寫入所有的字節數組。

    Tag

    msgByteSize

    msgByte

    9. bytes類型(ByteString),先用變長編碼寫入長度,然后寫入整個字節數組。

    Tag

    msgByteSize

    msgByte

    10. 對枚舉類型(類型值WIRETYPE_VARINT),用int32編碼方式寫入定義枚舉項時給定的值(因而在給枚舉類型項賦值時不推薦使用負數,因為int32編碼方式對負數編碼效率太低)。

    11. 對內嵌Message類型(類型值WIRETYPE_LENGTH_DELIMITED),先寫入整個Message序列化后字節長度,然后寫入整個Message

    Tag

    msgByteSize

    msgByte

    注:ZigZag編碼實現:(n << 1) ^ (n >> 31) / (n << 1) ^ (n >> 63);在CodedOutputStream中還存在一些用于計算某個字段可能占用的字節數的compute靜態方法,這里不再詳述。

    protobuf的序列化中,所有的類型最終都會轉換成一個可變長int/long類型、固定長度的int/long類型、byte類型以及byte數組。對byte類型的寫只是簡單的對內部buffer的賦值:

    public void writeRawByte(final byte value) throws IOException {
      if (position == limit) {
        refreshBuffer();
      }
      buffer[position++] = value;
    }

    32位可變長整形實現為:

    public void writeRawVarint32(int value) throws IOException {
      while (true) {
        if ((value & ~0x7F) == 0) {
          writeRawByte(value);
          return;
        } else {
          writeRawByte((value & 0x7F) | 0x80);
          value >>>= 7;
        }
      }
    }
    對于定長,protobuf采用小端模式,如對32位定長整形的實現:    
    public void writeRawLittleEndian32(final int value) throws IOExcep-tion {
        writeRawByte((value      ) & 0xFF);
        writeRawByte((value >>  8) & 0xFF);
        writeRawByte((value >> 16) & 0xFF);
        writeRawByte((value >> 24) & 0xFF);
    }

    byte數組,可以簡單理解為依次調用writeRawByte()方法,只是CodedOutputStream在實現時做了部分性能優化。這里不詳細介紹。
    CodedInputStream則是根據CodedOutputStream的編碼方式進行解碼,因而也不詳述,其中關于ZigZag的解碼:(n >>> 1) ^ -(n & 1)

    repeated字段編碼

    對于repeated字段,一般有兩種編碼方式:

    1.     每個項都先寫入tag,然后寫入具體數據。如對基本類型:

    Tag

    Data

    Tag

    Data

    而對message類型:

    Tag

    Length

    Data

    Tag

    Length

    Data

    2.     先寫入tag,后count,再寫入count個項,每個項包含length|data數據。即:

    Tag

    Count

    Length

    Data

    Length

    Data

    從編碼效率的角度來看,個人感覺第二中情況更加有效,然而不知道處于什么原因考慮,protobuf采用了第一種方式來編碼,個人能想到的一個理由是第一種情況下,每個消息項都是相對獨立的,因而在傳輸過程中接收端每接收到一個消息項就可以進行解析,而不需要等待整個repeated字段的消息包。對于基本類型,protobuf也采用了第一種編碼方式,后來發現這種編碼方式效率太低,因而可以添加[packed = true]的描述將其轉換成第三種編碼方式(第二種方式的變種,對基本數據類型,比第二種方式更加有效):
    3. 先寫入tag,后寫入字段的總字節數,再寫入每個項數據。即:

    Tag

    dataByteSize

    Data

    Data

    目前protobuf只支持基本類型的packed修飾,因而如果將packed添加到非repeated字段或非基本類型的repeated字段,編譯器在編譯.proto文件時會報錯。

    未識別字段編碼

    protobuf中,將所有未識別字段保存在UnknownFieldSet中,并且在每個由protobuf編譯生成的Message類以及GeneratedMessage.Builder中保存了UnknownFieldSet字段unknownFields;該字段可以從CodedInputStream中初始化(調用UnknownFieldSet.BuildermergeFieldFrom()方法)或從用戶自己通過Builder設置;在序列化時,調用UnknownFieldSetwriteTo()方法將自身內容序列化到CodedOutputStream中。

    UnknownFieldSet顧名思義是未知字段的集合,其內部數據結構是一個FieldNumberFieldMap,而一個Field用于表達一個未知字段,它可以是任何值,因而它包含了所有5中類型的List字段,這里并沒有對一個Field驗證,因而允許多個相同FieldNumber的未知字段,并且他們可以是任意類型值。UnknownFieldSet采用MessageLite編程模式,因而它實現了MessageLite接口,并且定義了一個Builder類實現MessageLite.Builder接口用于手動或從CodedInputStream中構建UnknownFieldSet。雖然Field本身沒有實現MessageLite接口,它依然實現了該接口的部分方法,如writeTo()getSerializedSize()用于實現向CodedOutputStream中序列化自身,并且定義了Field.Builder類用于構建Field實例。

    在一個Message序列化時(writeTo()方法實現),在寫完所有可識別的字段以及擴展字段,這個定義在Message中的UnknownFieldSet也會被寫入CodedOutputStream中;而在從CodedInputStream中解析時,對任何未知字段也都會被寫入這個UnknownFieldSet中。



    擴展字段編碼

    在寫框架代碼時,經常由擴展性的需求,在Java中,只需要簡單的定義一個父類或接口即可解決,如果框架本身還負責構建實例本身,可以使用反射或暴露Factory類也可以順利實現,然而對序列化來說,就很難提供這種動態plugin機制了。然而protobuf還是提出來一個相對可以接受的機制(語法有點怪異,但是至少可以用):在一個message中定義它支持的可擴展字段值的范圍,然后用戶可以使用extend關鍵字擴展該message定義(具體參考相關章節)。在實現中,所有這些支持字段擴展的message類型繼承自ExtendableMessage類(它本身繼承自GeneratedMessage類)并實現ExtendableMessageOrBuilder接口,而它們的Builder類則繼承自ExtendableBuilder類并且同時也實現了ExtendableMessageOrBuilder接口。

    ExtendableMessageExtendableBuilder類都包含FieldSet<FieldDescriptor>類型的字段用于保存該message所有的擴展字段值。FieldSet中保存了FieldDescriptor到其Object值的Map,然而在ExtendableMessageExtendableBuilder中則使用GeneratedExtension來表識一個擴展字段,這是因為GeneratedExtension除了包含對一個擴展字段的描述信息FieldDescriptor外,還存儲了該擴展字段的類型、默認值等信息,在protobuf消息定義編譯器中會為每個擴展字段生成相應的GeneratedExtension實例以供用戶使用:
    public static final GeneratedExtension<Foo, Integer> bar = Generated-Message.newFileScopedGeneratedExtension( Integer.classnull );

    bar.internalInit(descriptor.getExtensions().get(0));

    Base base = Base.newBuilder().setExtension(SearchRequestProtos.bar, 11).build();
    用戶使用該bar靜態字段用于作為key與它對應的值關聯,這種關聯關系寫入extensions字段中。從而在序列化時,對每個字段,按正常的值字段先寫Tag在寫實際值內容將它序列化到CodedOutputStream中(ExtensionWriter.writeUntil()方法);在反序列化中,我們需要告訴protobuf哪些字段是擴展字段,從而它在解析到無法識別的字段可以判斷這個字段是否是擴展字段,因而protobuf提供了ExtensionRegistry類,它用于注冊所有識別的擴展字段,并且在protobuf編譯出來的代碼中也存在一個靜態方法將所有已定義的擴展字段注冊到用戶提供的ExtensionRegistry實例中:    
    public static void registerAllExtensions(ExtensionRegistry registry) {
      registry.add(SearchRequestProtos.bar);
    }

    posted on 2015-04-01 09:23 DLevin 閱讀(11400) 評論(1)  編輯  收藏 所屬分類: Protobuf

    FeedBack:
    # re: 深入Protobuf源碼-編碼實現
    2015-04-29 13:50 | 陶瓷十大品牌
    深入淺出,不錯的介紹。  回復  更多評論
      
    主站蜘蛛池模板: 国产精品成人无码免费| 青青草97国产精品免费观看| 黄色永久免费网站| 亚洲最大视频网站| 真人做A免费观看| 亚洲人成在线播放| 一个人免费观看在线视频www| 国产精品亚洲精品观看不卡| 国产人在线成免费视频| 亚洲香蕉久久一区二区| 一个人免费观看视频www| 亚洲综合一区国产精品| 日韩精品视频免费网址| AV激情亚洲男人的天堂国语| 又粗又硬又黄又爽的免费视频| 国产区图片区小说区亚洲区| 免费一级毛片不卡不收费| 日本视频免费观看| 亚洲中文字幕无码日韩| 美女视频黄a视频全免费网站色窝| 亚洲阿v天堂在线| 久久久久久AV无码免费网站下载| 亚洲高清美女一区二区三区| 97碰公开在线观看免费视频| 自拍日韩亚洲一区在线| 日日夜夜精品免费视频| 美女被暴羞羞免费视频| 激情综合色五月丁香六月亚洲| 精品免费视在线观看| 亚洲福利一区二区三区| 免费无码精品黄AV电影| 久久久久久亚洲av无码蜜芽| 亚洲乱码中文字幕手机在线| 国产免费AV片在线观看 | 亚洲久悠悠色悠在线播放| 曰皮全部过程视频免费国产30分钟| 污污视频免费观看网站| 中国亚洲女人69内射少妇| 香蕉成人免费看片视频app下载| 91亚洲国产成人久久精品| 午夜视频免费成人|