支持類型
該表顯示了在?.proto
?文件中指定的類型,以及自動生成的類中的相應(yīng)類型:
.proto Type | Notes | C++ Type | Java/Kotlin Type[1] Java/Kotlin 類型 [1] | Python Type[3] | Go Type | Ruby Type | C# Type | PHP Type | Dart Type |
---|---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | double | |
float | float | float | float | float32 | Float | float | float | double | |
int32 | varint編碼。對于負(fù)數(shù)編碼效率低下——如果字段可能有負(fù)值,建議改用 sint32。 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
int64 | varint編碼。對于負(fù)數(shù)編碼效率低下——如果字段可能有負(fù)值,建議改用 sint64。 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 |
uint32 | varint編碼。 | uint32 | int | int/long | uint32 | Fixnum or Bignum (as required) | uint | integer | int |
uint64 | varint編碼。 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string | Int64 |
sint32 | zigzag和varint編碼。有符號的 int 值。比常規(guī)的 int32 能更高效地編碼負(fù)數(shù)。 | int32 | int | int | int32 | Fixnum or Bignum (as required) ) | int | integer | int |
sint64 | zigzag和varint編碼。有符號的 int 值。比常規(guī)的 int64 能更高效地編碼負(fù)數(shù)。 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 |
fixed32 | 總是四個字節(jié)。如果值通常大于 2\(^{28}\) ,則比 uint32 更有效。 | uint32 | int | int/long | uint32 | Fixnum or Bignum (as required) | uint | integer | int |
fixed64 | 總是八個字節(jié)。如果值通常大于 2\({^56}\) ,則比 uint64 更有效。 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string | Int64 |
sfixed32 | 總是四個字節(jié)。 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
sfixed64 | 總是八個字節(jié)。 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool | |
string | 字符串必須始終包含 UTF-8 編碼或 7 位 ASCII 文本,并且不能長于 2\(^{32}\) 。 | string | String | str/unicode | string | String (UTF-8) | string | string | String |
bytes | 可以包含任何不超過 2\(^{32}\) 的任意字節(jié)序列。 | string | ByteString | str (Python 2) bytes (Python 3) | []byte | String (ASCII-8BIT) | ByteString | string | List |
消息結(jié)構(gòu)
對于傳統(tǒng)的 xml 或者 json 等方式的序列化中,編碼時直接將 key 本身加進(jìn)去,例如:
{
"foo": 1,
"bar": 2
}
這樣最大的好處就是可讀性強(qiáng),但是缺點(diǎn)也很明顯,傳輸效率低,每次都需要傳輸重復(fù)的字段名。Protobuf 使用了另一種方式,將每一個字段進(jìn)行編號,這個編號被稱為 field number
。通過 field_number
的方式解決 json 等方式重復(fù)傳輸字段名導(dǎo)致的效率低下問題,例如:
message {
int32 foo = 1;
string bar = 2;
}
field_number
的類型被稱為wire types,目前有六種類型:VARINT
,?I64
,?LEN
,?SGROUP
,?EGROUP
, and?I32
(注:類型3和4已廢棄),因此需要至少3位來區(qū)分:
ID | Name | Used For |
---|---|---|
0 | VARINT | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | I64 | fixed64, sfixed64, double |
2 | LEN | string, bytes, embedded messages, packed repeated fields |
3 | SGROUP | group start (deprecated) |
4 | EGROUP | group end (deprecated) |
5 | I32 | fixed32, sfixed32, float |
當(dāng) message 被編碼時,每一個 key-value 包含 <tag> <type> <paylog>
,其結(jié)構(gòu)如下:
+--------------+-----------+---------+
| field_number | wire_type | payload |
+--------------+-----------+---------+
| | |
| | | +---------------+
+---------------+ +--------->| (length) data |
| tag | +---------------+
+---------------+
- field_number 和 wire_type 被稱為 tag,使用一個字節(jié)來表示(這里指編碼前的一個字節(jié),通過Varint編碼后可能并非一個字節(jié))。其值為
(field_number << 3) | wire_type
,換句話說低3位解釋了wire_type,剩余的位則解釋了field_number。 - payload 則為 value 具體值,根據(jù) wire_type 的類型決定是否是采用 Length-Delimited 記錄
額外一提的是由于 tag 結(jié)構(gòu)如上所述,因此對于使用 Varint 編碼的 1個字節(jié)來說去除最高位標(biāo)志位和低三位保留給 wire_type使用,剩下四位能夠表示[0, 15] 的字段標(biāo)識,超過則需要使用多于一個字節(jié)來存儲 tag 信息,因此盡可能將頻繁使用的字段的字段標(biāo)識定義在 [0, 15] 直接。
編碼規(guī)則
Protobuf 使用一種緊湊的二進(jìn)制格式來編碼消息。編碼規(guī)則包括以下幾個方面:
- 每個字段都有一個唯一的標(biāo)識符和一個類型,標(biāo)識符和類型信息一起構(gòu)成了字段的 tag。
- 字段的 tag 采用 Varint 編碼方式進(jìn)行編碼,可以節(jié)省空間。
- 字符串類型的字段采用長度前綴方式進(jìn)行編碼,先編碼字符串的長度,再編碼字符串本身。
- 重復(fù)的字段可以使用 repeated 關(guān)鍵字進(jìn)行定義,編碼時將重復(fù)的值按照順序編碼成一個列表。
Varint 編碼
Varint 是一種可變長度的編碼方式,可以將一個整數(shù)編碼成一個字節(jié)序列。值越小的數(shù)字,使用越少的字節(jié)數(shù)表示。它的原理是通過減少表示數(shù)字的字節(jié)數(shù)從而實(shí)現(xiàn)數(shù)據(jù)體積壓縮。
Varint 編碼的規(guī)則如下:
- 對于值小于 128 的整數(shù),直接編碼為一個字節(jié);
- 對于值大于等于 128 的整數(shù),將低 7 位編碼到第一個字節(jié)中,將高位編碼到后續(xù)的字節(jié)中,并在最高位添加一個標(biāo)志位(1 表示后續(xù)還有字節(jié),0 表示當(dāng)前字節(jié)是最后一個字節(jié))。每個字節(jié)的最高位也稱 MSB(most significant bit)。
在解碼的時候,如果讀到的字節(jié)的 MSB 是 1 話,則表示還有后序字節(jié),一直讀到 MSB 為 0 的字節(jié)為止。
例如,int32類型、field_number為1、值位 300 的 Varint 編碼為:
// 300 的二進(jìn)制
00000001 00101100
// 按7位切割
00 0000010 0101100
// 高位全0省略
0000010 0101100
// 逆序,使用的小端字節(jié)序
0101100 0000010
// 每一組加上msb,除了最后一組是msb是0,其他的都為1
10101100 00000010
// 十六進(jìn)制指
ac 02
// 按照 protobuf 的消息結(jié)構(gòu),其完整位
08 ac 02
| |__|__ payload
|
|----------- tag (field-number << 3 | wire-type) = (1 << 3 | 0) = 0x08
ZigZag編碼
對于 int32/int64
的 proto type,值大于 0 時直接使用 Varint 編碼,而值為負(fù)數(shù)時做了符號拓展,轉(zhuǎn)換為 int64
的類型,再做 Varint 編碼。負(fù)數(shù)高位為1,因此對于負(fù)數(shù)固定需要十個字節(jié)( ceil(64 / 7) = 10
)。(這里有個值得思考的問題是對于 int32 類型的負(fù)數(shù)為什么要轉(zhuǎn)換為 int64
來處理?不轉(zhuǎn)換的話使用5個字節(jié)就能夠完成編碼了。網(wǎng)上的一個說法是為了轉(zhuǎn)換為 int64 類型時沒有兼容性問題,此處由于還未閱讀過源碼,不知道內(nèi)部是怎么處理的,因此暫時也沒想通為什么因為兼容性問題需要做符號拓展。因為按照 Varint 編碼規(guī)則解碼的話,直接讀取出來的值賦值給 int64 的類型也沒有問題。int32 negative numbers)
很明顯,這樣對于負(fù)數(shù)的編碼是非常低效的。因此 protobuf 引入 sint32
和 sint64
,在編碼時先將數(shù)字使用 ZigZag
編碼,然后再使用 Varint
編碼。
ZigZag 編碼將有符號數(shù)映射為無符號數(shù),對應(yīng)的編解碼規(guī)則如下:
static uint32_t ZigZagEncode32(int32_t v) {
// Note: the right-shift must be arithmetic
// Note: left shift must be unsigned because of overflow
return (static_cast<uint32_t>(v) << 1) ^ static_cast<uint32_t>(v >> 31);
}
static uint64_t ZigZagEncode64(int64_t v) {
// Note: the right-shift must be arithmetic
// Note: left shift must be unsigned because of overflow
return (static_cast<uint64_t>(v) << 1) ^ static_cast<uint64_t>(v >> 63);
}
int32_t ZigZagDecode32(uint32_t n) {
// Note: Using unsigned types prevent undefined behavior
return static_cast<int32_t>((n >> 1) ^ (~(n & 1) + 1));
}
static int64_t ZigZagDecode64(uint64_t n) {
// Note: Using unsigned types prevent undefined behavior
return static_cast<int64_t>((n >> 1) ^ (~(n & 1) + 1));
}
因此如果傳輸?shù)臄?shù)據(jù)中可能包含有負(fù)數(shù),那么應(yīng)該使用 sint32/sint64
類型。因為 protobuf 中只定義了為這兩種數(shù)據(jù)類型進(jìn)行 ZigZag
編碼再使用 Varint
編碼。文章來源:http://www.zghlxwxcb.cn/news/detail-431863.html
Length-delimited 編碼
wire_type
為 LEN
,由于其具有動態(tài)長度,因此其由一個 Length 值保存長度大小,這個 Length 同樣通過 Varint 編碼,最后是其內(nèi)容。
參照以下例子:文章來源地址http://www.zghlxwxcb.cn/news/detail-431863.html
message Test2 {
optional string b = 2;
}
b = "testing"
12 07 [74 65 73 74 69 6e 67]
| | t e s t i n g
| | |__|__|__|__|__|__ body 的 ASCII 碼
| |
| |__ length = 6 = 0x06
|
|__ Tag (field-number << 3 | wire-type) = (2 << 3 | 2) = 18 = 0x12
到了這里,關(guān)于Protobuf編碼規(guī)則的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!