序列化格式

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。

其它整数被表示为如下格式:

  1. i<n>;

标记 i 表示整数的开始,标记 ; 表示整数的结束。

<n> 是数字 n 的字符串表示。

例如:

  1. 0 # 整数 0
  2. 8 # 整数 8
  3. i1234567; # 整数 1234567
  4. i-128; # 整数 -128

Long

长整型数 n 表示为如下格式:

  1. l<n>;

标记 l 表示长整型数的开始,标记 ; 表示长整型数的结束。

<n> 是数字 n 的字符串表示。

例如:

  1. l1234567890987654321; # 长整型数 1234567890987654321
  2. l-987654321234567890; # 长整型数 -987654321234567890

Double

浮点数具有三个特殊值:非数字(NaN),正无穷大和负无穷大。

非数字(NaN)被表示为一个字节:'N'

正无穷大被表示为两个字节:'I+'

负无穷大被表示为两个字节:'I-'

其它浮点数 n 被表示为以下格式:

  1. d<n>;

标记 d 表示浮点数的开始,标记 ; 表示浮点数的结束。

<n> 是数字 n 的字符串表示。

例如:

  1. N # 非数字(NaN)
  2. I+ # 正无穷大(Infinity)
  3. I- # 负无穷大(-Infinity)
  4. d3.1415926535898; # 浮点数 3.1415926535898
  5. d-0.1; # 浮点数 -0.1
  6. d-1.45E23; # 浮点数 -1.45E23
  7. d3.76e-54; # 浮点数 3.76e-54

指数符号 e 是不区分大小写的。

Boolean

单个字节 't' 表示真值(true),单个字节 'f' 表示假值(false)。

  1. t # true
  2. f # false

UTF8 char

大部分语言拥有一个 Unicode 字符类型。比如 Java 和 C#。UTF8 char 用于存储此种类型。所以你无法存储所有的 Unicode 代码点(codepoint)。如果一个 Unicode 代码点(codepoint)需要两个字符来存储。它将被序列化为一个 String

字符 c 表示为如下形式:

  1. u<c>

标记 u 表示 UTF8 char 的开始。没有结束标记,因为 UTF8 char 是自描述的。

<c> 是 utf8 编码的字符。例如:

  1. uA # 'A' 被存储为 1 个字节 0x41
  2. u½ # '½' 被存储为 2 个字节 0xC2 0xBD
  3. u # '∞' 被存储为 3 个字节 0xE2 0x88 0x9E

Null

Null 表示一个 null 指针或 null 对象。单字节 'n' 表示 null 值。

  1. n # null

Empty

Empty 表示一个空的字符串或空的二进制数据。单字节 'e' 表示 empty 值。

  1. e # empty

DateTime

DateTime 在 hprose 是引用类型,尽管在某些语言中它可能是一个值类型。这并不会带来任何问题。我们将会在后面引用 一节来讨论值类型和引用类型的区别。

DateTime 可以表示本地或 UTC 时间。本地时间以标记 ; 作为结尾,UTC 时间以标记 Z 作为结尾。

DateTime 数据可以包含年,月,日,时,分,秒,毫秒,微秒,纳秒,但并不需要全部都包含。他可以只包含年、月、日来表示日期,或者只包含时、分、秒来表示时间。

标记 D 表示日期的开始,标记 T 表示时间的开始。

例如:

  1. D20121229; # 本地日期 2012-12-29
  2. D20121225Z # UTC 日期 2012-12-25
  3. T032159; # 本地时间 03:21:59
  4. T182343.654Z # UTC 时间 18:23:43.654
  5. D20121221T151435Z # UTC 日期时间 2012-12-21 15:14:35
  6. D20501228T134359.324543123; # 本地日期时间 2050-12-28 13:43:59.324543123

Bytes

Bytes 类型表示二进制数据。它相当于编程语言中的 byte[] 或数据流对象。

Bytes 的最大长度为 2147483647。

二进制数据 bytes (假设它的长度为 len) 表示为如下形式:

  1. b<len>"<bytes>"

标记 b 表示二进制数据的开始。如果 <len> 为 0,<len> 可以被省略。<bytes> 是原始的二进制数据。标记 " 用来表示二进制数据的开始和结束。

例如:

  1. b"" # 空的二进制数据
  2. b10"!@#- %^&*()" # byte[10] { '!', '@', '#', '- ', '%', '^', '&', '*', '(', ')' }

String

String 类型表示 Unicode 字符组成的字符串或者字符数组,或者 UTF8 编码的字符串或字符数组。

字符串的最大长度为 2147483647。该长度既非字节数,也非 Unicode 代码点个数。它表示的是 16 位的 Unicode(UTF-16 编码)的字符个数。

字符串 str (假设它的长度为 len) 表示为如下形式:

  1. s<len>"<str>"

标记 s 表示字符串的开始。如果 <len> 为 0, <len> 可以被忽略。<str> 是 UTF-8 编码的字符串。标记 " 表示字符串的开始与结束。

例如:

  1. s"" # 空的字符串
  2. s12"Hello world!" # 字符串 'Hello world!'
  3. s2"你好" # 字符串 '你好'

GUID

GUID 是“全局唯一标识符”(Globally Unique Identifier)的缩写。它相当于编程语言中的 GUID 或 UUID 类型。

GUID 是一个 128 位的值。它表示为如下形式:

  1. g{<GUID>}

标记 g 表示 GUID 的开始。标记 {} 用来表示 GUID 数据的开始和结束。<GUID> 是一个格式化为 32 个十六进制数字并通过连字符(-)按组分隔的字符串,比如:AFA7F4B1-A64D-46FA-886F-ED7FBCE569B6。

例如:

  1. 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 被表示为如下形式:

  1. a<n>{<元素_1><元素_2><元素_3>...<元素_n>}

标记 a 表示 List 的开始。如果 <n> 为 0,<n> 可以被省略。标记 {} 被用来表示 List 数据的开始和结束。<元素_i> 是每个元素序列化后的数据。元素之间没有其它任何分隔符,因为序列化数据是自描述的。

例如:

  1. a{} # 0 个元素的数组
  2. a10{0123456789} # int[10] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
  3. a7{s3"Mon"s3"Tue"s3"Wed"s3"Thu"s3"Fri"s3"Sat"s3"Sun"} # string[7] {'Mon, 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'}
  4. 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 表示为如下形式:

  1. 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)和键值对之间没有其它任何分隔符,因为序列化数据是自描述的。

例如:

  1. m{} # 0 个键值对的 Map
  2. m2{s4"name"s5"Tommy"s3"age"i24;} # {
  3. # "name": "Tommy",
  4. # "age" : 24
  5. # }

Object

Object 与 Map 类似,但是 Object 的键必须为字符串,并且 Object 具有一个固定的结构。在这里,我们将这个固定的结构称为类(Class)。这里的类(Class)相当于编程语言中的结构体(struct),类(class),或对象原型(object prototype)。

Object 的序列化格式比 Map 要复杂的多。它被分为两个部分:

  • 类(Class)的序列化
  • 对象实例(Object instance)的序列化

类(Class)的序列化包括类型名称,字段/属性个数,字段/属性名称。类(Class)的序列化只进行一次。后面的对象只需要序列化它们的数据值。

每个类拥有一个整数引用编号,它将被对象实例的序列化所引用。

类的整数引用编号从 0 开始。它表示:如果有许多不同的对象属于不同的类,那么当序列化类时,第一个序列化的类编号为 0,第二个序列化的类的编号为 1,以此类推。

类的序列化格式如下:

  1. c<类型名称长度>"<类型名称的字符串>"<字段数目>{<字段 1 的名称><字段 2 的名称>...<字段 n 的名称>}

对象实例序列化的格式如下:

  1. o<类的整数引用编号>{<字段 1 的值><字段 2 的值>...<字段 n 的值>}

例如:

  1. class Person {
  2. String name;
  3. int age;
  4. }
  5.  
  6. Person[] users = new Person[2];
  7.  
  8. users[0] = new Person("Tommy", 24);
  9. users[1] = new Person("Jerry", 19);

users 的序列化数据为:

  1. a2{c6"Person"2{s4"name"s3"age"}o0{s5"Tommy"i24;}o0{s5"Jerry"i19;}}

引用

我们知道 JSON 是一个轻量级的数据格式。但是 JSON 不能表示下面的数据:

  1. var list = [];
  2. list[0] = list;

因为 JSON 不支持引用。但是 hprose 可以将其序列化为:

  1. a1{r0;}

每个引用类型的数据拥有一个整数引用编号,但它与类的整数引用编号是相互独立的。

引用类型数据的整数引用编号从 0 开始。当相同的数据被再次序列化时,它将被序列化为一个引用。

但是请注意,引用本身不具有整数引用编号。

引用数据表示为以下形式:

  1. r<整数引用编号>;

标记 r 表示引用的开始。标记 ; 表示引用的结束。

例如:

  1. var list = [
  2. {
  3. "name": "Tommy",
  4. "age" : 24
  5. },
  6. {
  7. "name": "Jerry",
  8. "age" : 18
  9. }
  10. ];

list 的序列化数据为:

  1. a2{m2{s4"name"s5"Tommy"s3"age"i24;}m2{r2;s5"Jerry"r4;i18;}}

再举一例:

  1. var a = [];
  2. var b = [];
  3. a[0] = a;
  4. a[1] = b;
  5. b[0] = a;
  6. b[1] = b;
  7. var c = [a,b];

c 的序列化数据为:

  1. a2{a2{r1;a2{r1;r2;}}r2;}