序列化格式
Hprose 序列化拥有 7 种值类型:
- Integer (32位有符号整型数)
- Long (无限精度长整型数)
- Double (float, double or decimal)
- Boolean
- UTF8 char (16位 Unicode 字符, UTF-8 格式, 1-3 个字节)
- Null
- Empty (空的字符串, 空的二进制数据)
4 种简单引用类型:
和 3 种可递归引用类型:
每种类型以一个标记或者多个标记与数据混合表示。每个标记占一个字节(8 位)。
标记是区分大小写的。序列化数据是空白敏感的。
Integer
如果整数 n
满足 0 ≤ n ≤ 9,那幺序列化数据被表示为一个字节,数值为:0x30 + n。
其它整数被表示为如下格式:
- i<n>;
标记 i
表示整数的开始,标记 ;
表示整数的结束。
<n>
是数字 n 的字符串表示。
例如:
- 0 # 整数 0
- 8 # 整数 8
- i1234567; # 整数 1234567
- i-128; # 整数 -128
Long
长整型数 n
表示为如下格式:
- l<n>;
标记 l
表示长整型数的开始,标记 ;
表示长整型数的结束。
<n>
是数字 n 的字符串表示。
例如:
- l1234567890987654321; # 长整型数 1234567890987654321
- l-987654321234567890; # 长整型数 -987654321234567890
Double
浮点数具有三个特殊值:非数字(NaN),正无穷大和负无穷大。
非数字(NaN)被表示为一个字节:'N'
。
正无穷大被表示为两个字节:'I+'
。
负无穷大被表示为两个字节:'I-'
。
其它浮点数 n
被表示为以下格式:
- d<n>;
标记 d
表示浮点数的开始,标记 ;
表示浮点数的结束。
<n>
是数字 n 的字符串表示。
例如:
- N # 非数字(NaN)
- I+ # 正无穷大(Infinity)
- I- # 负无穷大(-Infinity)
- d3.1415926535898; # 浮点数 3.1415926535898
- d-0.1; # 浮点数 -0.1
- d-1.45E23; # 浮点数 -1.45E23
- d3.76e-54; # 浮点数 3.76e-54
指数符号 e
是不区分大小写的。
Boolean
单个字节 't'
表示真值(true),单个字节 'f'
表示假值(false)。
- t # true
- f # false
UTF8 char
大部分语言拥有一个 Unicode 字符类型。比如 Java 和 C#。UTF8 char 用于存储此种类型。所以你无法存储所有的 Unicode 代码点(codepoint)。如果一个 Unicode 代码点(codepoint)需要两个字符来存储。它将被序列化为一个 String。
字符 c
表示为如下形式:
- u<c>
标记 u
表示 UTF8 char 的开始。没有结束标记,因为 UTF8 char 是自描述的。
<c>
是 utf8 编码的字符。例如:
- uA # 'A' 被存储为 1 个字节 0x41
- u½ # '½' 被存储为 2 个字节 0xC2 0xBD
- u∞ # '∞' 被存储为 3 个字节 0xE2 0x88 0x9E
Null
Null 表示一个 null 指针或 null 对象。单字节 'n'
表示 null 值。
- n # null
Empty
Empty 表示一个空的字符串或空的二进制数据。单字节 'e'
表示 empty 值。
- e # empty
DateTime
DateTime 在 hprose 是引用类型,尽管在某些语言中它可能是一个值类型。这并不会带来任何问题。我们将会在后面引用 一节来讨论值类型和引用类型的区别。
DateTime 可以表示本地或 UTC 时间。本地时间以标记 ;
作为结尾,UTC 时间以标记 Z
作为结尾。
DateTime 数据可以包含年,月,日,时,分,秒,毫秒,微秒,纳秒,但并不需要全部都包含。他可以只包含年、月、日来表示日期,或者只包含时、分、秒来表示时间。
标记 D
表示日期的开始,标记 T
表示时间的开始。
例如:
- D20121229; # 本地日期 2012-12-29
- D20121225Z # UTC 日期 2012-12-25
- T032159; # 本地时间 03:21:59
- T182343.654Z # UTC 时间 18:23:43.654
- D20121221T151435Z # UTC 日期时间 2012-12-21 15:14:35
- D20501228T134359.324543123; # 本地日期时间 2050-12-28 13:43:59.324543123
Bytes
Bytes 类型表示二进制数据。它相当于编程语言中的 byte[] 或数据流对象。
Bytes 的最大长度为 2147483647。
二进制数据 bytes
(假设它的长度为 len
) 表示为如下形式:
- b<len>"<bytes>"
标记 b
表示二进制数据的开始。如果 <len>
为 0,<len>
可以被省略。<bytes>
是原始的二进制数据。标记 "
用来表示二进制数据的开始和结束。
例如:
- b"" # 空的二进制数据
- b10"!@#- %^&*()" # byte[10] { '!', '@', '#', '- ', '%', '^', '&', '*', '(', ')' }
String
String 类型表示 Unicode 字符组成的字符串或者字符数组,或者 UTF8 编码的字符串或字符数组。
字符串的最大长度为 2147483647。该长度既非字节数,也非 Unicode 代码点个数。它表示的是 16 位的 Unicode(UTF-16 编码)的字符个数。
字符串 str
(假设它的长度为 len
) 表示为如下形式:
- s<len>"<str>"
标记 s
表示字符串的开始。如果 <len>
为 0, <len>
可以被忽略。<str>
是 UTF-8 编码的字符串。标记 "
表示字符串的开始与结束。
例如:
- s"" # 空的字符串
- s12"Hello world!" # 字符串 'Hello world!'
- s2"你好" # 字符串 '你好'
GUID
GUID 是“全局唯一标识符”(Globally Unique Identifier)的缩写。它相当于编程语言中的 GUID 或 UUID 类型。
GUID 是一个 128 位的值。它表示为如下形式:
- g{<GUID>}
标记 g
表示 GUID 的开始。标记 {
}
用来表示 GUID 数据的开始和结束。<GUID>
是一个格式化为 32 个十六进制数字并通过连字符(-)按组分隔的字符串,比如:AFA7F4B1-A64D-46FA-886F-ED7FBCE569B6。
例如:
- g{AFA7F4B1-A64D-46FA-886F-ED7FBCE569B6} # GUID 'AFA7F4B1-A64D-46FA-886F-ED7FBCE569B6'
GUID 字符串是不区分大小写的。
List
List 是一个可递归引用类型。它相当于编程语言中的数组(array),列表(list),集合(set)或容器(collection)类型。
它可以包含 0 个或多个元素。元素可以为 hprose 中任意有效的类型。
List 元素的最大数目为 2147483647。
具有 n
个元素的 List 被表示为如下形式:
- a<n>{<元素_1><元素_2><元素_3>...<元素_n>}
标记 a
表示 List 的开始。如果 <n>
为 0,<n>
可以被省略。标记 {
}
被用来表示 List 数据的开始和结束。<元素_i>
是每个元素序列化后的数据。元素之间没有其它任何分隔符,因为序列化数据是自描述的。
例如:
- a{} # 0 个元素的数组
- a10{0123456789} # int[10] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
- a7{s3"Mon"s3"Tue"s3"Wed"s3"Thu"s3"Fri"s3"Sat"s3"Sun"} # string[7] {'Mon, 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'}
- a3{a3{123}a3{456}a3{789}} # int[][] { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }
Map
Map 是另一种可递归引用类型。它相当于编程语言中的表(map),哈希表(hashtable),字典(dictionary)或动态对象类型。
它可以包含 0 个或多个键值对。键(key)或值(value)可以为 hprose 中任意有效的类型。
Map 中键值对的最大数目为 2147483647。
包含有 n
个键值对的 Map 表示为如下形式:
- m<n>{<key_1><value_1><key_2><value_2>...<key_n><value_n>}
标记 m
表示 Map 的开始。如果 <n>
为 0,<n>
可以被省略。标记 {
}
被用来表示 Map 数据的开始和结束。<key_i>
是每个键(key)序列化后的数据。<value_i>
是每个值(value)序列化后的数据。键(key)、值(value)和键值对之间没有其它任何分隔符,因为序列化数据是自描述的。
例如:
- m{} # 0 个键值对的 Map
- m2{s4"name"s5"Tommy"s3"age"i24;} # {
- # "name": "Tommy",
- # "age" : 24
- # }
Object
Object 与 Map 类似,但是 Object 的键必须为字符串,并且 Object 具有一个固定的结构。在这里,我们将这个固定的结构称为类(Class)。这里的类(Class)相当于编程语言中的结构体(struct),类(class),或对象原型(object prototype)。
Object 的序列化格式比 Map 要复杂的多。它被分为两个部分:
- 类(Class)的序列化
- 对象实例(Object instance)的序列化
类(Class)的序列化包括类型名称,字段/属性个数,字段/属性名称。类(Class)的序列化只进行一次。后面的对象只需要序列化它们的数据值。
每个类拥有一个整数引用编号,它将被对象实例的序列化所引用。
类的整数引用编号从 0 开始。它表示:如果有许多不同的对象属于不同的类,那幺当序列化类时,第一个序列化的类编号为 0,第二个序列化的类的编号为 1,以此类推。
类的序列化格式如下:
- c<类型名称长度>"<类型名称的字符串>"<字段数目>{<字段 1 的名称><字段 2 的名称>...<字段 n 的名称>}
对象实例序列化的格式如下:
- o<类的整数引用编号>{<字段 1 的值><字段 2 的值>...<字段 n 的值>}
例如:
- class Person {
- String name;
- int age;
- }
- Person[] users = new Person[2];
- users[0] = new Person("Tommy", 24);
- users[1] = new Person("Jerry", 19);
users 的序列化数据为:
- a2{c6"Person"2{s4"name"s3"age"}o0{s5"Tommy"i24;}o0{s5"Jerry"i19;}}
引用
我们知道 JSON 是一个轻量级的数据格式。但是 JSON 不能表示下面的数据:
- var list = [];
- list[0] = list;
因为 JSON 不支持引用。但是 hprose 可以将其序列化为:
- a1{r0;}
每个引用类型的数据拥有一个整数引用编号,但它与类的整数引用编号是相互独立的。
引用类型数据的整数引用编号从 0 开始。当相同的数据被再次序列化时,它将被序列化为一个引用。
但是请注意,引用本身不具有整数引用编号。
引用数据表示为以下形式:
- r<整数引用编号>;
标记 r
表示引用的开始。标记 ;
表示引用的结束。
例如:
- var list = [
- {
- "name": "Tommy",
- "age" : 24
- },
- {
- "name": "Jerry",
- "age" : 18
- }
- ];
list 的序列化数据为:
- a2{m2{s4"name"s5"Tommy"s3"age"i24;}m2{r2;s5"Jerry"r4;i18;}}
再举一例:
- var a = [];
- var b = [];
- a[0] = a;
- a[1] = b;
- b[0] = a;
- b[1] = b;
- var c = [a,b];
c 的序列化数据为:
- a2{a2{r1;a2{r1;r2;}}r2;}