MessagePack簡析
一、MessagePack是什么
先看官方的定義:MessagePack是一種高效的二進制序列化格式。它允許您像JSON一樣在多個語言之間交換數據。但是,它更快并且更小。
從官方定義中,可以有如下的結論:
-
MessagePack是一個二進制序列化格式,因而它序列化的結果可以在多個語言間進行數據的交換。
-
從性能上講,它要比json的序列化格式要好。
-
從結果大小上講,它要比json的序列化結果要小。
但是官方并沒有提MessagePack和google pb的對比,實際上從空間和時間兩個方面對比,pb均要優(yōu)于MessagePack,但pb相對MessagePack 的缺點是支持的語言種類比較少,需要編寫專門的 .proto文件,使用上沒有MessagePack方便。
二、MessagePack的主要概念
2.1 type system
類型體系是MessagePack的基礎,也是MessagePack在序列化后比json占用空間小的關鍵。當前包含的type有如下幾類:
-
Integer represents an integer
-
Nil represents nil
-
Boolean represents true or false
-
Float represents a IEEE 754 double precision floating point number including NaN and Infinity
-
Raw
-
String extending Raw type represents a UTF-8 string
-
Binary extending Raw type represents a byte array
-
-
Array represents a sequence of objects
-
Map represents key-value pairs of objects
-
Extension represents a tuple of type information and a byte array where type information is an integer whose meaning is defined by applications or MessagePack specification
-
Timestamp represents an instantaneous point on the time-line in the world that is independent from time zones or calendars. Maximum precision is nanoseconds.
-
這個類型體系將我們在代碼開發(fā)中用到的數據格式進行了映射,并且通過Extension這個類型給使用者留出了自由擴充的空間,但由于表示形式的限制,當前Extension最多有127個。
每一種類型能夠表示的范圍可以查看MessagePack規(guī)范中的Limitation部分和Extension types部分。
2.2 formats
在MessagePack中一個value的組成格式是這樣的:類型[長度][data]。下面列出幾個示例,詳細完整的描述請看附錄中的MessagePack規(guī)范。
2.2.1常量型
比如對于null、true、false這三個值,在MessagePack會被固定的映射為如下的值。
|
format name
|
first byte (in binary)
|
first byte (in hex)
|
|
nil
|
11000000
|
0xc0
|
|
false
|
11000010
|
0xc2
|
|
true
|
11000011
|
0xc3
|
2.2.2 int型(包含有符號整數和無符號整數)
示例如下
-
0xcc表示當前的值的類型是無符號整數并且長度不超過8個bit,具體的值內容需要通過后續(xù)8個bit位的內容來計算
-
0xcd表示當前的值的類型是無符號整數并且長度不超過16個bit,具體的值內容需要通過后續(xù)16個bit位的內容來計算
-
0xd0表示當前的值的類型是有符號整數并且長度不超過8個bit,具體的值內容需要通過后續(xù)8個bit位的內容來計算
-
0xd1表示當前的值的類型是有符號整數并且長度不超過16個bit,具體的值內容需要通過后續(xù)16個bit位的內容來計算
uint 8 stores a 8-bit unsigned integer +--------+--------+ | 0xcc |ZZZZZZZZ| +--------+--------+ uint 16 stores a 16-bit big-endian unsigned integer +--------+--------+--------+ | 0xcd |ZZZZZZZZ|ZZZZZZZZ| +--------+--------+--------+ int 8 stores a 8-bit signed integer +--------+--------+ | 0xd0 |ZZZZZZZZ| +--------+--------+ int 16 stores a 16-bit big-endian signed integer +--------+--------+--------+ | 0xd1 |ZZZZZZZZ|ZZZZZZZZ| +--------+--------+--------+
2.2.3 字符串
-
0xd9表示當前的值的類型是字符串并且長度不超過(2^8)-1個bytes ,具體的長度需要通過后續(xù)8個bit位的內容來計算,字符串的具體內容是后續(xù)長度的byte所表示的內容
-
0xda表示當前的值的類型是字符串并且長度不超過(2^16)-1個bytes ,具體的長度需要通過后續(xù)16個bit位的內容來計算,字符串的具體內容是后續(xù)長度的byte所表示的內容
str 8 stores a byte array whose length is upto (2^8)-1 bytes: +--------+--------+========+ | 0xd9 |YYYYYYYY| data | +--------+--------+========+ str 16 stores a byte array whose length is upto (2^16)-1 bytes: +--------+--------+--------+========+ | 0xda |ZZZZZZZZ|ZZZZZZZZ| data | +--------+--------+--------+========+
2.2.4 數組
-
0xdc表示當前的值的類型是數組并且長度不超過(2^16)-1個元素 ,具體的長度需要通過后續(xù)16個bit位(兩個byte)的內容來計算,計算出來的值就是數組元素的個數
-
0xdd表示當前的值的類型是數組并且長度不超過(2^32)-1個元素 ,具體的長度需要通過后續(xù)32個bit位(4個byte)的內容來計算,計算出來的值就是數組元素的個數
array 16 stores an array whose length is upto (2^16)-1 elements: +--------+--------+--------+~~~~~~~~~~~~~~~~~+ | 0xdc |YYYYYYYY|YYYYYYYY| N objects | +--------+--------+--------+~~~~~~~~~~~~~~~~~+ array 32 stores an array whose length is upto (2^32)-1 elements: +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+ | 0xdd |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ| N objects | +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+
2.2.5 小結


2.3 Serialization:type to format conversion
|
source types
|
output format
|
|
Integer
|
int format family (positive fixint, negative fixint, int 8/16/32/64 or uint 8/16/32/64)
|
|
Nil
|
nil
|
|
Boolean
|
bool format family (false or true)
|
|
Float
|
float format family (float 32/64)
|
|
String
|
str format family (fixstr or str 8/16/32)
|
|
Binary
|
bin format family (bin 8/16/32)
|
|
Array
|
array format family (fixarray or array 16/32)
|
|
Map
|
map format family (fixmap or map 16/32)
|
|
Extension
|
ext format family (fixext or ext 8/16/32)
|
If an object can be represented in multiple possible output formats, serializers SHOULD use the format which represents the data in the smallest number of bytes.
2.4 Deserialization: format to type conversion
|
source formats
|
output type
|
|
positive fixint, negative fixint, int 8/16/32/64 and uint 8/16/32/64
|
Integer
|
|
nil
|
Nil
|
|
false and true
|
Boolean
|
|
float 32/64
|
Float
|
|
fixstr and str 8/16/32
|
String
|
|
bin 8/16/32
|
Binary
|
|
fixarray and array 16/32
|
Array
|
|
fixmap map 16/32
|
Map
|
|
fixext and ext 8/16/32
|
Extension
|
三、為什么MessagePack比json序列化使用的字節(jié)流更少
3.1 直觀對比
可以通過下圖的兩張圖簡單進行下對比,第一張圖是同一個數據類型的內容用json和messagepack序列化的結果。


從第二張圖可以明顯看到messagepack要比json占用的空間更少。
3.2 序列化結果只有value且value進行了專屬映射

這張圖是MessagePack官網上的,用來進行json和MessagePack序列化結果的對比,實際情況是否確實如此呢?
我本地使用的msgpack-0.6.12版本。
代碼如下:
public class MessagePackSerializationCompareJson {
@Message // Annotation
public static class MyMessage {
// public fields are serialized.
public boolean compact;
public int schema;
public String toString() {
return "compact:"+compact+";schema:"+schema;
}
}
/**
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//初始化一個對象
MyMessage src = new MyMessage();
src.compact = true;
src.schema=0;
//利用MessagePack進行序列化
MessagePack msgpack = new MessagePack();
// Serialize
byte[] bytes = msgpack.write(src);
System.out.println("msgpack result length:"+bytes.length);
//利用json進行序列化
String jsonResult = JSONObject.toJSON(src).toString();
System.out.println("json result length:"+jsonResult.getBytes().length);
}
}
運行結果如下:
json result length:27 msgpack result length:3
json序列化的結果是27,和官網圖片中的結果相同。但MessagePack的序列化結果是3,要比官網中的數字小很多。
按照上面圖片的解釋應當是:
-
第一個byte是82,表示序列化后的結果有兩個元素
-
第二個byte是c3,表示第一個元素的值是true
-
第三個byte是00,表示第二個元素的值是0
為了驗證我們的推測,我們可以在MyMessage類中再添加一個boolean類型的屬性,但不給這個屬性賦值,按照java的規(guī)范,這個屬性的值就是false,按照MessagePack的規(guī)范,就會被轉為一個byte的c2,這樣msgpack序列化后的長度值就是4. 而json序列化的增加值要增加不少,是屬性名稱的長度+5(false的長度)+4(要增加兩個雙引號,一個逗號,一個冒號),如果屬性名稱長度是4,則一共會增加13個byte,總長度就是40.
public class MessagePackSerializationCompareJson {
@Message // Annotation
public static class MyMessage {
// public fields are serialized.
public boolean compact;
public int schema;
public boolean link;
public String toString() {
return "compact:"+compact+";schema:"+schema+";link:"+link;
}
}
/**
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//初始化一個對象
MyMessage src = new MyMessage();
src.compact = true;
src.schema=0;
//利用json進行序列化
String jsonResult = JSONObject.toJSON(src).toString();
System.out.println("json result length:"+jsonResult.getBytes().length);
//利用MessagePack進行序列化
MessagePack msgpack = new MessagePack();
// Serialize
byte[] bytes = msgpack.write(src);
System.out.println("msgpack result length:"+bytes.length);
}
}
上面代碼的執(zhí)行的結果也符合猜測:
json result length:40 msgpack result length:4
從這個數字上看,MessagePack明顯優(yōu)于json,特別是在屬性多的情況下差距會更大。即使json中把key去掉,序列化后的結果也要比MessagePack占用的空間大。
3.2 序列化對象的屬性順序不能變動
3.1分析了MessagePack序列化的結果中只包含了value,而不包含key。因而在進行反序列化需要保證類中屬性的順序必須保證完全一致,否則就會出錯:
如果兩個屬性的類型一致,可以反序列化,但是值發(fā)生錯亂。
如果兩個屬性的類型不一致,會拋出類型不匹配異常。
3.2.1 順序不同,類型相同
public class SimpleMessagePackPractice {
@Message // Annotation
public static class MyMessage {
// public fields are serialized.
public boolean compact;
public boolean link;
public String toString() {
return "link:" + link + ";compact:" + compact;
}
}
@Message // Annotation
public static class MyMessage2 {
// public fields are serialized.
public boolean link;
public boolean compact;
public String toString() {
return "link:" + link + ";compact:" + compact;
}
}
/**
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//初始化一個對象
MyMessage src = new MyMessage();
src.compact = true;
src.link = false;
//利用MessagePack進行序列化
MessagePack msgpack = new MessagePack();
// Serialize
byte[] bytes = msgpack.write(src);
//利用MessagePack進行反序列化
MyMessage2 dst = msgpack.read(bytes, MyMessage2.class);
System.out.println("msgpack 原始數據:" + src);
System.out.println("msgpack 反序列化:" + dst);
}
}
上述代碼的執(zhí)行結果如下:
msgpack 原始數據:link:false;compact:true
msgpack 反序列化:link:true;compact:false
3.2.1 順序不同,類型不同
public class SimpleMessagePackPractice {
@Message // Annotation
public static class MyMessage {
// public fields are serialized.
public boolean compact;
public String link;
public String toString() {
return "link:" + link + ";compact:" + compact;
}
}
@Message // Annotation
public static class MyMessage2 {
// public fields are serialized.
public String link;
public boolean compact;
public String toString() {
return "link:" + link + ";compact:" + compact;
}
}
/**
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//初始化一個對象
MyMessage src = new MyMessage();
src.compact = true;
src.link = "www.baidu.com";
//利用MessagePack進行序列化
MessagePack msgpack = new MessagePack();
// Serialize
byte[] bytes = msgpack.write(src);
//利用MessagePack進行反序列化
MyMessage2 dst = msgpack.read(bytes, MyMessage2.class);
System.out.println("msgpack 原始數據:" + src);
System.out.println("msgpack 反序列化:" + dst);
}
}
執(zhí)行結果:
Exception in thread "main" org.msgpack.MessageTypeException: Expected raw value, but got boolean at org.msgpack.unpacker.Accept.acceptBoolean(Accept.java:33) at org.msgpack.unpacker.MessagePackUnpacker.readOneWithoutStackLarge(MessagePackUnpacker.java:154) at org.msgpack.unpacker.MessagePackUnpacker.readOneWithoutStack(MessagePackUnpacker.java:139) at org.msgpack.unpacker.MessagePackUnpacker.readOne(MessagePackUnpacker.java:73) at org.msgpack.unpacker.MessagePackUnpacker.readString(MessagePackUnpacker.java:502) at org.msgpack.template.StringTemplate.read(StringTemplate.java:46) at org.msgpack.template.StringTemplate.read(StringTemplate.java:25) at org.msgpack.template.AbstractTemplate.read(AbstractTemplate.java:31) at com.my.msgpack.SimpleMessagePackPractice$MyMessage2_$$_Template_1305193908_1.read(SimpleMessagePackPractice$MyMessage2_$$_Template_1305193908_1.java) at org.msgpack.template.AbstractTemplate.read(AbstractTemplate.java:31) at org.msgpack.MessagePack.read(MessagePack.java:388) at org.msgpack.MessagePack.read(MessagePack.java:371) at com.my.msgpack.SimpleMessagePackPractice.main(SimpleMessagePackPractice.java:61)
參考資料
-
MessagePack specification https://github.com/msgpack/msgpack/blob/master/spec.md#type-system
-
浙公網安備 33010602011771號