PackStream

PackStream 是一种用于交换富类型数据的二进制表示格式。它为 Bolt 消息协议提供了一个语法层。

版本 1

PackStream 是一种通用数据序列化格式,最初受到 MessagePack 的启发(但不兼容)。

该格式提供了一个与 Cypher 支持的类型完全兼容的类型系统,更多信息请参阅 Cypher 手册 → 值和类型

PackStream 提供了一系列核心数据类型,其中许多类型支持多种二进制表示,并具备灵活的扩展机制。

核心数据类型如下表所示。

表 1. 核心数据类型
数据类型 描述

Null

缺失或空值

布尔值

truefalse

整数

带符号 64 位整数

浮点数

64 位浮点数

Bytes

字节数组

字符串

Unicode 文本,UTF-8

列表

值的有序集合

Dictionary

键值对集合(不保证顺序)

Structure

带类型签名的复合值

不包含无符号整数和 32 位浮点数。这是一个深思熟虑的设计决策,旨在允许跨客户端语言实现更广泛的兼容性。

通用表示

每个序列化的 PackStream 值都以一个标记字节 (marker byte) 开头。

该标记包含数据类型信息,以及对于需要该信息的类型,包含直接或间接的大小信息。大小信息的编码方式因标记类型而异。

某些值(例如布尔值 true)可以在单个标记字节内编码。许多小整数(具体为 -16 到 +127 之间)也编码在单个字节内。

部分标记字节被保留用于格式本身的未来扩展。不应使用这些字节,在传入的数据流中遇到它们应视为错误。

定长值 (Sized values)

某些表示具有可变长度,并在表示中显式编码了其大小。此类值通常以一个标记字节开头,后跟一个大小,再后跟数据内容本身。在此上下文中,标记同时表示类型和规模,因此决定了用于表示数据大小的字节数。大小本身编码为 8 位、16 位或 32 位无符号整数。不支持超过此长度的大小。

下图展示了定长值的一般布局,此处以 16 位大小为例

packstream sized value

字节序 (Endianness)

PackStream 仅使用大端序 (big-endian) 表示。这意味着值的最高有效部分首先被写入网络或内存空间,而最低有效部分最后被写入。

数据类型

Null

标记: C0

Null 始终使用单个标记字节 C0 进行编码。

Boolean

标记, false: C2

标记, true: C3

布尔值在单个标记字节内编码,使用 C3 表示 true,使用 C2 表示 false

Integer

标记, TINY_INT

标记 十进制数

F0

-16

F1

-15

F2

-14

F3

-13

F4

-12

F5

-11

F6

-10

F7

-9

F8

-8

F9

-7

FA

-6

FB

-5

FC

-4

FD

-3

FE

-2

FF

-1

00

0

01

1

02

2

…​

…​

…​

…​

…​

…​

7E

126

7F

127

标记, INT_8: C8

标记, INT_16: C9

标记, INT_32: CA

标记, INT_64: CB

根据数值大小,整数值占用 1、2、3、5 或 9 个字节。可用的表示形式为

表示 大小 (字节) 描述

TINY_INT

1

仅标记字节

INT_8

2

标记字节 C8 后跟带符号 8 位整数

INT_16

3

标记字节 C9 后跟带符号 16 位整数

INT_32

5

标记字节 CA 后跟带符号 32 位整数

INT_64

9

标记字节 CB 后跟带符号 64 位整数

下文展示了可用的编码,每个编码都显示了十进制值 42 的有效表示

表示 大小 (字节) 描述

TINY_INT

1

2A

INT_8

2

C8 2A

INT_16

3

C9 00 2A

INT_32

5

CA 00 00 00 2A

INT_64

9

CB 00 00 00 00 00 00 00 2A

某些标记字节既可用于表示小整数的值,也可用于表示其类型。这些标记可以通过高位比特为 0(对于正值)或高位半字节仅包含 1(对于负值)来识别。具体来说,007F(含)之间的值可以直接转换为相同值的正整数。同样,F0FF(含)之间的值可以同样转换为 -16 到 -1 之间的负数。

虽然可以使用更宽的格式来编码小数,但通常建议使用尽可能紧凑的表示形式。

下表显示了带符号 64 位范围内每个可能整数的最佳表示形式

范围最小值 范围最大值 最佳表示

-9 223 372 036 854 775 808

-2 147 483 649

INT_64

-2 147 483 648

-32 769

INT_32

-32 768

-129

INT_16

-128

-17

INT_8

-16

+127

TINY_INT

+128

+32 767

INT_16

+32 768

+2 147 483 647

INT_32

+2 147 483 648

+9 223 372 036 854 775 807

INT_64

最小值示例

-9223372036854775808(最小值)可以表示为

CB 80 00 00 00 00 00 00 00
最大值示例

9223372036854775807(最大值)可以表示为

CB 7F FF FF FF FF FF FF FF

Float

标记: C1

浮点数是双精度浮点值,通常用于表示分数和小数。它们编码为单个 C1 标记字节,后跟 8 个字节,这些字节根据 IEEE 754 浮点数“双精度格式”位布局以大端序排列。

  • 第 63 位(由掩码 0x8000000000000000 选择的位)代表数字的符号。

  • 第 62-52 位(由掩码 0x7ff0000000000000 选择的位)代表指数。

  • 第 51-0 位(由掩码 0x000fffffffffffff 选择的位)代表数字的尾数(有时称为 mantissa)。

十进制值示例

十进制值 1.23 可以表示为

C1 3F F3 AE 14 7A E1 47 AE

Bytes

字节是字节值的数组。它们用于传输原始二进制数据,大小表示包含的字节数。与其他值不同,对于包含少于 16 个字节的字节数组,没有单独的编码。

标记 大小 最大大小

CC

8 位大端序无符号整数

255 字节

CD

16 位大端序无符号整数

65 535 字节

CE

32 位大端序无符号整数

2 147 483 647 字节

应根据规模使用标记 CCCDCE 之一。此标记后跟大小和字节本身。

注意:虽然 CE 后面的 32 位无符号整数可以容纳更大的数字,但字节数组的最大大小限制为 2 147 483 647(带符号 32 位整数的最大值)。

空字节数组示例

空字节数组 b[]

CC 00
字节数组中有三个值的示例

包含三个值 1、2 和 3 的字节数组;b[1, 2, 3]

CC 03 01 02 03

String

标记

对于较短字符串

标记 大小 (字节)

80

0

81

1

82

2

83

3

84

4

85

5

86

6

87

7

88

8

89

9

8A

10

8B

11

8C

12

8D

13

8E

14

8F

15

对于较长字符串

标记 大小 最大字节数

D0

8 位大端序无符号整数

255 字节

D1

16 位大端序无符号整数

65 535 字节

D2

32 位大端序无符号整数

2 147 483 647 字节

文本数据表示为 UTF-8 编码的字节。

字符串表示中用到的大小是 UTF-8 编码数据的字节数,而不是原始文本的字符数。

对于包含少于 16 个字节的编码文本(包括空字符串),标记字节应包含高位半字节 8(二进制 1000),后跟包含大小的低位半字节。编码数据紧随标记之后。对于包含 16 个或更多字节的编码文本,应根据规模使用标记 D0D1D2。此标记后跟大小和 UTF-8 编码数据。

注意:虽然 D3 后面的 32 位无符号整数可以容纳更大的数字,但字符串的最大字节大小限制为 2 147 483 647(带符号 32 位整数的最大值)。

表 2. 不同字符串的示例
编码

String("")

80

String("A")

81 41

String("ABCDEFGHIJKLMNOPQRSTUVWXYZ")

D0 1A 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A

String("Größenmaßstäbe")

D0 12 47 72 C3 B6 C3 9F 65 6E 6D 61 C3 9F 73 74 C3 A4 62 65

List

列表是值的异构序列,因此允许在同一个列表中混合使用不同类型。列表的大小表示该列表中的项目数量,而不是总的打包字节大小。

标记

标记 大小 (项目) 最大大小

90

标记的低位半字节

0 个项目

91

标记的低位半字节

1 个项目

92

标记的低位半字节

2 个项目

93

标记的低位半字节

3 个项目

94

标记的低位半字节

4 个项目

95

标记的低位半字节

5 个项目

96

标记的低位半字节

6 个项目

97

标记的低位半字节

7 个项目

98

标记的低位半字节

8 个项目

99

标记的低位半字节

9 个项目

9A

标记的低位半字节

10 个项目

9B

标记的低位半字节

11 个项目

9C

标记的低位半字节

12 个项目

9D

标记的低位半字节

13 个项目

9E

标记的低位半字节

14 个项目

9F

标记的低位半字节

15 个项目

D4

8 位大端序无符号整数

255 个项目

D5

16 位大端序无符号整数

65 535 个项目

D6

32 位大端序无符号整数

2 147 483 647 个项目

对于包含少于 16 个项目的列表(包括空列表),标记字节应包含高位半字节 9(二进制 1001),后跟包含大小的低位半字节。列表中的项目随后按顺序序列化在标记之后。

对于包含 16 个或更多项目的列表,应根据规模使用标记 D4D5D6。此标记后跟大小和按顺序序列化的列表项目。

注意:虽然 D6 后面的 32 位无符号整数可以容纳更大的数字,但列表的最大大小限制为 2 147 483 647(带符号 32 位整数的最大值)。

0 个项目示例
[]
90
3 个项目示例
[Integer(1), Integer(2), Integer(3)]
93 01 02 03
包含三种不同类型项目的示例
[
  Integer(1),
  Float(2.0),
  String("three")
]
93
01
C1 40 00 00 00 00 00 00 00
85 74 68 72 65 65
包含超过 15 个项目的示例
[
    Integer(1),
    Integer(2),
    ...
    Integer(40)
]
D4 28
01 02 03 04 05 06 07 08 09 0A
0B 0C 0D 0E 0F 10 11 12 13 14
15 16 17 18 19 1A 1B 1C 1D 1E
1F 20 21 22 23 24 25 26 27 28

Dictionary

Dictionary 是包含键值对条目的列表

  • 键必须是 String

  • 可以包含同一个键的多个实例

  • 允许混合类型

Dictionary 的大小表示该字典中的键值对条目数量,而不是总的打包字节大小。

标记

标记 大小 (键值对条目) 最大大小

A0

包含在标记的低位半字节中

0

A1

包含在标记的低位半字节中

1

A2

包含在标记的低位半字节中

2

A3

包含在标记的低位半字节中

3

A4

包含在标记的低位半字节中

4

A5

包含在标记的低位半字节中

5

A6

包含在标记的低位半字节中

6

A7

包含在标记的低位半字节中

7

A8

包含在标记的低位半字节中

8

A9

包含在标记的低位半字节中

9

AA

包含在标记的低位半字节中

10

AB

包含在标记的低位半字节中

11

AC

包含在标记的低位半字节中

12

AD

包含在标记的低位半字节中

13

AE

包含在标记的低位半字节中

14

AF

包含在标记的低位半字节中

15

D8

8 位大端序无符号整数

255 条目

D9

16 位大端序无符号整数

65 535 条目

DA

32 位大端序无符号整数

2 147 483 647 条目

对于包含少于 16 个键值对条目的字典(包括空字典),标记字节应包含高位半字节 A(二进制 1010),后跟包含大小的低位半字节。

字典中的条目随后按 [key, value, key, value] 的顺序序列化在标记之后。

键始终是 String 值。

对于包含 16 个或更多键值对条目的字典,应根据规模使用标记 D8D9DA。此标记后跟大小和键值对条目。

注意:虽然 DA 后面的 32 位无符号整数可以容纳更大的数字,但字典的最大大小限制为 2 147 483 647(带符号 32 位整数的最大值)。

空字典示例
{}
A0
两个条目的示例
{"one": "eins"}
A1 83 6F 6E 65 84 65 69 6E 73
超过 15 个条目的示例
{"A": 1, "B": 2 ... "Z": 26}
D8 1A
81 41 01 81 42 02 81 43 03 81 44 04
81 45 05 81 46 06 81 47 07 81 48 08
81 49 09 81 4A 0A 81 4B 0B 81 4C 0C
81 4D 0D 81 4E 0E 81 4F 0F 81 50 10
81 51 11 81 52 12 81 53 13 81 54 14
81 55 15 81 56 16 81 57 17 81 58 18
81 59 19 81 5A 1A

如果在解包时存在同一个键的多个实例,应使用该键最后出现的值。

示例
[("key_1", 1), ("key_2", 2), ("key_1", 3)] -> {"key_1": 3, "key_2": 2}

Structure

结构是一种复合值,由字段和唯一类型代码组成。除标记外,结构编码由一个单字节(标签字节)组成,后跟最多 15 个字段,每个字段是一个独立的值。结构的大小以字段数来衡量,而不是总字节大小。此计数不包含标签。

标记

标记 大小 (字段) 最大大小

B0

包含在标记的低位半字节中

0 个字段

B1

包含在标记的低位半字节中

1 个字段

B2

包含在标记的低位半字节中

2 个字段

B3

包含在标记的低位半字节中

3 个字段

B4

包含在标记的低位半字节中

4 个字段

B5

包含在标记的低位半字节中

5 个字段

B6

包含在标记的低位半字节中

6 个字段

B7

包含在标记的低位半字节中

7 个字段

B8

包含在标记的低位半字节中

8 个字段

B9

包含在标记的低位半字节中

9 个字段

BA

包含在标记的低位半字节中

10 个字段

BB

包含在标记的低位半字节中

11 个字段

BC

包含在标记的低位半字节中

12 个字段

BD

包含在标记的低位半字节中

13 个字段

BE

包含在标记的低位半字节中

14 个字段

BF

包含在标记的低位半字节中

15 个字段

对于包含少于 16 个字段的结构,标记字节应包含高位半字节 B(二进制 1011),后跟包含大小的低位半字节。标记后紧跟标签字节和按该顺序排列的字段值。标签字节用于标识结构的类型或类,并可包含 0 到 +127 之间的任何值。

PackStream 本身不为不同的结构定义语义。请参阅相关 Bolt 版本的 结构语义 (Structure Semantics)