-
Notifications
You must be signed in to change notification settings - Fork 0
/
第 12.09.02 章、encoding_gob 包
476 lines (377 loc) · 19.6 KB
/
第 12.09.02 章、encoding_gob 包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
总览
gob 包管理 gob 组成的数据流——在一个 Encode(发送方) 和一个 Decoder(接受方)之间传输的二进制值。
典型的用法是传输远程程序调用(RPC)的参数和结果,比如被包 “net/rpc” 所提供的内容。
该实现将数据流中的每种数据类型都定义了一个自定义编号,且该方法在使用单一 Encoder 发送数据流时最为高效,因为分担了编译的开销。
基础
gob 流是自解释的。数据流中的每个数据项前都有其类型的说明,该说明表示为一小组预定义的类型。
指针不会被传输,而传输它指向的内容;也就是说,数据会被平面化。
Nil 指针不被允许,因为它们不含任何值。
递归类型是没有问题的,但递归值(循环数据)会产生问题。这个状况可能会有所改观。
要使用 gob,创建一个 Encoder 并提供一系列数据项,包含值或可解析为值的指针。
Encoder 保证了所有的类型信息在使用前都被传输了。
在接受端,一个 Decoder 从编码的数据流中检索值,并解包至他们的本地变量中。
类型和值
源和目标值/类型并不需要完全一一对应。
对于 stuct,存在于原数据但在接收时缺失的字段(以名称区分)将会被忽略。
存在于目标的变量但在发送段缺失时,也会被忽略。
若在两者之中存在同名的字段,则它们的类型必须兼容。
接收端和发送段都会进行必要的引用和解引用将 gob 和实际的 Go 值之间转换。
举例来说,一个被规划为下述的 gob 类型,
struct { A,B int }
可以从下述 Go 类型中发送和接收:
struct { A, B int } // 完全一致
*struct { A, B int } // 对结构体的引用
struct { *A, **B int } // 对字段的引用
struct { A, B int64 } // 不同的实体值类型;见下方
也可以被接受为下述这些:
struct { A, B int } // 完全一致
struct { B, A int } // 顺序无关;仅匹配字段
struct { A, B, C int } // 多余的字段 (C) 被忽略
struct { B int } // 缺失的字段 (A) 被忽略;数据被丢弃
struct { B, C int } // 缺失的字段 (A) 被忽略;多余的字段 (C) 被忽略
接收至下述类型将产生一个解码错误:
struct { A int; B uint } // 修改了 B 的符号
struct { A int; B float } // 修改了 B 的类型
struct { } // 不含任何共同字段名
struct { C, D int } // 不含任何共同字段名
整数用两种方法传递:任意精度的符号整型和任意进度的无符号整型。
在 gob 类型中,没有 int8 和 int16 等之间的区分;仅有代符号整型和无符号整型。
如下方描述的,发送方以变长编码发送值;接收端接收值并将其存入目标变量。
浮点数永远以 IEEE-754 64 比特进度发送(见下方)。
符号整型可以被接收至任何符号整型变量中:int,int64 等;无符号整型可以被接收至任何无符号整型中;浮点数值可以被任何浮点数值接收。
但是,目标变量必须具有表示该值的能力,否则解码操作也会失败。
同样支持结构体、数组和切片。结构体仅会编码和解码导出字段。
字符串和字节数组支持特定的高效表示方法(见下)。
当一个切片解码时,若现存的切片容量足够,则扩展当前切片;若不够,则分配一个新的切片。
无论如何,返回的切片的长度都报告了解码的元素的个数。
通常来说,若需要执行分配,解码器将分配内存。若不需要,它将会从数据流中解码数据并更新目标变量。
它不会预先初始化它们,所以,若目标是复合值,比如字典、结构体或切片,解码的值将在元素层级与原有变量融合。
函数和通道将不通过 gob 传送。尝试在顶层编码该类值将失败。作为结构体字段的通道或者函数类型将作为非导出字段对待,会直接被忽略。
gob 将以以下顺序选择编码方式:
1、实现了 GobEncoder
2、实现了 encoding.BinaryMarshaler
且调用它们来实际执行编码
gob 将以以下顺序选择解码方式:
1、实现了 GobDecoder
2、实现了 encoding.BinaryUnmarshaler
且调用它们来实际执行编码
编码细节
这部分记录了编码过程,细节对于大多数用户来说都不重要。细节以由底至顶的样式呈现。
一个无符号整数以两种方式中的一种发送。若它小于 128,则以单字节发送该值。
除此之外它以最小长度的大端序(高位字节优先)字节流存储值,前序跟随一字节的字节长度计数的反码。
因此 0 以 (00) 发送, 7 以 (07) 发送,256 以 (FE 01 00) 发送。
布尔值以无符号整型编码发送:0 为 假,1 为 真。
一个符号整型 i,被编码为一个无符号整型 u。在 u 中,bit 1 及以上的包含数据,bit 0 表示是否应该执行取补运算。
编码算法看起来如下:
var u uint
if i < 0 {
u = (^uint(i) << 1) | 1 // i 的补码,bit 0 为 1
} else {
u = uint(i) << 1 // 不执行取补码,bit 0 为 0
}
encodeUnsigned(u)
因此,低位 bit 等同于一个符号 bit,但用于设置补码 bit,而非保证最大的负数不为特例。
举例来说, -129=^128=(^256>>1) 编码为 (FE 01 01)
浮点数永远以 float64 值表示传送。该值将会以 math.Float64bits 转换为 uint64。这个 uint64 值会进行字节逆序,并以普通无符号整型发送。
字节逆序意味着指数和小数的高精度部分会先编码。这是由于低位 bit 通常为 0,这样可以节省编码字节。
举例来说,17.0 仅用三字节即可编码 (FE 31 40)。
字符串和字节切片将以无符号统计数,后接那么多字节的未解释数据。
任何其他的切片和数组都表示为无符号统计数,后接那么多个元素,每个元素都递归地以它自己的标准 gob 编码表示。
字典表示为无符号计数,后接那么多个键值对。空的但非 nil 的字典会被发送,所以若接收端并未分配一个,则永远会分配一个,除非发出的字典为 nil 且不在顶层。
在切片、数组和字典中,所有元素,包括零值元素都会被发送,即便所有元素都是 0 也会被发送。
结构体以一系列(字段号,字段值)数据对。字段值递归使用其类型的标准 gob 编码。
若一个字段为其类型的零值,(除了数组,见上)则从传送中忽略。
字段号由被编码的结构体的类型来定义:被编码的类型的第一个字段为字段 0,第二个为字段 1,等等。
当编码一个值时,字段号出于效率以增量编号,而字段总是顺着字段号增加的顺序发送;增量因此是一个无符号整型。
初始化增量编号时,设置字段号为 -1,所以一个值为 7 的无符号整数字段 0,将以 无符号增量 = 0,无符号值 = 7 或者(01 07) 来发送。
最终,当所有的字段都被发送完毕时,将发送一个终止符,标示结构体的结束。这个标记时就是增量 = 0 的值,表示为 (00)。
接口类型为了兼容性不会被查验;所有的接口类型在传输时,都被当成单个 “interface” 的成员,和 int 或 []byte 类似 —— 实际上他们都被当成 interface{} 对待。
接口值以字符串定义的被发送的实际类型(必须通过调用 Register 预先定义一个名称),跟随 1 字节的其后数据长度(这样,若值无法被解析,就可以被跳过了),跟随保存在接口值中的通常编码的实际(动态)值。
(一个 nil 接口由一个空字符串定义,且不传输任何值)在接收端,解码器会验证被解包的实际项目满足了接收变量的接口。
若一个值发送给了 Encode,且其类型并非 struct(或者是指向 struct 的指针,等等),为了简化操作,它被表示为具有单一字段的结构体。
仅有的可见效果就是,在这个值编码结束之后,会添加一个值为 0 的字节,如同一个已被编码的结构体的最后一个字段之后,这样解码算法就知道何时顶层值已经完成。
下面描述了类型的表示方法。
在一对给定的 Encoder 和 Decoder 的连接中,定义了一个类型,这个类型就会赋予一个含符号整数类型 id。
当 Encoder.Encode(v) 被调用时,它保证一个 id 被赋予了 v 和 其所有元素对应的类型,之后,它发送对 (typeid, encoded-v),typeid 是 被编码的类型 v 的类型 id,encoded-v 是 gob 编码的值 v。
要定义一个类型,编码器选用一个未使用的,正整数类型 id,并发送 (-type id, encoded-type) 对,其中 encoded-type 是 gob 编码的一个 wireType 描述,从下述类型构建:
type wireType strcut {
ArrayT *ArrayType
SliceT *SliceType
StructT *StructType
MapT *MapType
}
type arrayType struct {
CommonType
Elem typeId
Len int
}
type CommonType struct {
Name string // 结构体类型的名称
Id int // 类型的 id,此处重复,这样它就会在类型内部
}
type sliceType struct {
CommonType
Elem typeId
}
type strutType struct {
CommonType
Field []*fieldType // 结构体的字段
}
type fieldType struct {
Name string // 字段的名称
Id int // 字段的类型 id,必须预先定义
}
type mapType struct {
CommonType
Key typeId
Elem typeId
}
若又嵌套的类型 id,所有用于定义内部类型 id 的类型必须先于顶层类型 id 描述 encoded-v 之前定义。
为了简化设置,连接被定义为了预先知道这些类型,如同基础的 gob 类型 int, uint 等等一样。这些 id 包括:
bool 1
int 2
uint 3
float 4
[]byte 5
string 6
complex 7
interface 8
// 保留 id 占位
WireType 16
ArrayType 17
CommonType 18
SliceType 19
StructType 20
FieldType 21
// 22 是 fieldType 的切片
MapType 23
最后,每个被调用 Encode 产生的信息前都会预置一个编码过后的无符号整数,记录该信息中剩余字节的数量。
在初始类型名之后,接口值也用相同方法包装起来;效果就是,接口值就像递归调用了 Encode 一样。
总的来说,一个 gob 数据流看起来是这样的
(字节数 (-type id, wireType 的编码)* (type id, 值编码))*
其中的 * 号表示零个或多个重复,且一个值的 type id 必须预先定义,或者在数据流的值之前被定义。
兼容性:任何未来对该包的修改,都将尽力维护与前序版本编码出来的数据流的兼容性。
也就是说,任何该包的发布版本,应该能够解码任意前序发布版本写出的数据,除非是修复安全问题。
参看 Go 兼容性文档来了解背景:https://golang.org/doc/go1compat
参看 “Gobs of data” 了解 gob wire format 的设计讨论: https://blog.golang.org/gobs-of-data
案例(基础)
该案例展示了该包的基础用法:创建一个编码器,传输数据,使用解码器接收它们。
package main
import (
"bytes"
"encoding/gob"
"fmt"
"log"
)
type P struct {
X, Y, Z int
Name string
}
type Q struct {
X, Y *int32
Name string
}
// 该案例展示了该包的基础用法:
// 创建一个编码器,传输数据,使用解码器接收它们。
func main() {
// 初始化编码器和解码器。
// 通常情况下, enc 和 dec 将与网络连接绑定
// 且编码器和解码器将在不同进程中执行
var network bytes.Buffer // 假设为网络连接
enc := gob.NewEncoder(&network) // 将写入网络
dec := gob.NewDecoder(&network) // 将从网络读取
// 编码(发送)一些值
err := enc.Encode(P{3, 4, 5, "Pythagoras"})
if err != nil {
log.Fatal("encode error:", err)
}
err = enc.Encode(P{1782, 1841, 1922, "Treehouse"})
if err != nil {
log.Fatal("encode error:", err)
}
// 解码(接收)并打印值
var q Q
err = dec.Decode(&q)
if err != nil {
log.Fatal("decode error 1:", err)
}
fmt.Printf("%q: {%d, %d}\n", q.Name, *q.X, *q.Y)
err = dec.Decode(%q)
if err != nil {
log.Fatal("decode error 2:", err)
}
fmt.Printf("%q: {%d, %d}\n", q.Name, *q.X, *q.Y)
}
案例(编码解码)
该案例传输了一个实现了自定义编码和解码方法的值
package main
import (
"bytes"
"encoding/gob"
"fmt"
"log"
)
// Vector 类型具有非导出字段,这些字段不能被本包识别。
// 因此我们写了一个 BinaryMarshal/BinaryUnmarshal 方法对让我们可以使用 gob 包来发送和接收该类型。
// 这些接口在 "encoding" 包中定义了。
// 我们也可以等价地使用本地定义的 GobEncoder/GobDecoder 接口。
type Vector struct {
x, y, z int
}
func (v Vector) MarshalBinary() ([]byte, error) {
// 一个简单的编码方式:纯文本编码。
var b bytes.Buffer
fmt.Fprintln(&b, v.x, v.y, v.z)
return b.Bytes(), nil
}
// UnmarshalBinary 修改了接收器,这样它必须使用一个指针接收者。
func (v *Vector) UnmarshalBinary(data []byte) error {
// 一个简单的编码方式:纯文本编码。
b := bytes.NewBuffer(data)
_, err := fmt.Fscanln(b, &v.x, &v.y, &v.z)
return err
}
// 该案例传输了一个实现了自定义编码和解码方法的值
func main() {
var network bytes.Buffer // 假设为网络连接
// 创建一个编码器并发送一个值
enc := gob.NewEncoder(&network)
err := enc.Encode(Vector{3, 4, 5})
if err != nil {
log.Fatal("encode:", err)
}
// 创建解码器并接收一个值
dec := gob.NewDecoder(&network)
var v Vector
err = dec.Decode(&v)
if err != nil {
log.Fatal("decode:", err)
}
fmt.Println(v)
}
案例(接口)
这个案例展示了如何编码一个接口值。与普通类型的关键不同在于注册实现了该接口的实际类型。
package main
import (
"bytes"
"encoding/gob"
"fmt"
"log"
"math"
)
type Point struct {
X, Y int
}
func (p Point) Hypotenuse() float64 {
return math.Hypot(float64(p.X), float64(p.Y))
}
type Pythagoras interface {
Hypotenuse() float64
}
// 这个案例展示了如何编码一个接口值。
// 与普通类型的关键不同在于注册实现了该接口的实际类型。
func main() {
var network bytes.Buffer // 假设为网络连接
// 我们必须必须为编码器和解码器注册实际类型(编码器和解码器一般位于不同的机器上)。
// 在每一侧,着都告诉了引擎发送的是哪个实现了该接口的实际类型
gob.Register(Point{})
// 创建一个编码器,并发送一些值
enc := gov.NewEncoder(&network)
for i := 1; i <= 3; i++ {
interfaceEncode(enc, Point{3 * i, 4 *i})
}
// 创建一个解码器,并接收一些值
dec := gob.NewDecoder(&network)
for i := 1; i <= 3; i++ {
result := interfaceDecode(dec)
fmt.Println(result.Hypotenuse())
}
}
// interfaceEncode 将接口值编码至编码器中。
func interfaceEncode(enc *gob.Encoder, p Pythagoras) {
// 除非实际类型已经被注册,否则编码器会失败。
// 我们已经在调用函数中注册过它了。
// 传入指向接口的指针,这样编码器看到的(也是发送的)值就是接口类型的。
// 如果我们直接传入 p,它看到的就是实际类型。
// 参见博客, "The Laws fi Reflection" 了解背景信息。
err := enc.Encode(&p)
if err != nil {
log.Fatal("encode:", err)
}
}
// interfaceDecode 从数据流中解码下一个接口值,并返回它。
func interfaceDecode(dec *gob.Decoder) Pythagoras {
// 除非在线的实际类型已经被注册,否则解码器会失败。
// 我们已经在调用函数中注册过它了。
var p Pythagoras
err := dec.Decode(&p)
if err != nil {
log.Fatal("decode:", err)
}
return p
}
func Register(value interface{})
记录一个类型,用该类型的值定义该类型,底层记录其内部的类型名称。
该名称将确定以接口值发送或传输的值的实际类型。
仅有当做接口值发送和接收的类型才需要注册。
期望仅在初始化时使用,若类型和名称之间的映射不是双射,则 panic
func RegisterName(name string, value interface{})
与 Register 类似,但使用给定的名称而非类型的默认名称
type CommonType
持有所有类型的元素。
这是一个历史遗留内容,为了二进制兼容性而保留,且仅在为了便于包编码类型描述符时导出。
并不期望客户端直接使用。
type CommonType struct {
Name string
Id typeId
}
type Decoder
用于处理读取自连接远端的的类型和数据信息。
Decoder 仅会简单的检查被解码的输入的大小,且它的局限性不可调整。
请在解码非可信来源的 gob 数据时加倍小心。
type Decoder struct {
// 包含过滤或未导出的字段
}
func NewDecoder(r io.Reader) *Decoder
返回一个新的从 io.Reader 解码信息的解码器。
若 r 并没有实现 io.ByteReader,它会被包裹在一个 bufio.Reader 中。
func (dec *Decoder) Decode(e interface{}) error
读取输入数据流中的下一个值,用空接口保存它的数据表示。
若 e 为 nil,则值将被忽略。
其他情况下,e 的底层值,必须是一个指向正确类型的指针,且该底层类型必须能被下一个解码后的数据对应。
若输入为 EOF,本函数返回 io.EOF,并且不去修改 e。
func (dec *Decoder) DecodeValue(v relfect.Value) error
从输入数据流中读取下一个值。
若 v 是 reflect.Value 的零值(v.Kind() == Invalid),本函数跳过该值。
其他情况下,e 的底层值,必须是一个指向正确类型的指针,且该底层类型必须能被下一个解码后的数据对应。
若输入为 EOF,本函数返回 io.EOF,并且不去修改 e。
type Encoder
将类型和数据信息发送至连接的另一侧
type Encoder struct {
// 包含过滤或未导出的字段
}
func NewEncoder(w io.Writer) *Encoder
返回一个将会对 io.Writer 发送数据的编码器
func (enc *Encoder) Encode(e interface{}) error
将空接口值表示的数据发送出去,保证所有必须的信息都首先传送了。
向本方法传入一个 nil 指针将导致 panic,因为它们不可以被 gob 传输。
func (enc *Encoder) EncodeValue(value reflect.Value) error
将反射值表示的数据发送出去,保证所有必须的信息都首先传送了。
向本方法传入一个 nil 指针将导致 panic,因为它们不可以被 gob 传输。
type GobDecoder
描述了一个其自身被 GobEncoder 自定义编码过后的数据,该如何被解码的接口
type GobDecoder interface {
// GobDecode 覆写了接收者,该接收者必须为一个指针,
// 以及用字节切片表示的值,该值由 GobEncode 产生,通常对应着相同的实际类型。
GobDecode([]byte) error
}
type GobEncoder
该接口描述了数据提供了编码自身值,发送给 GobDecoder 的数据。
一个实现了 GobEncoder 和 GobDecoder 的类型具有对表示其数据完全的控制权,
因此可以包含私有字段、通道、函数等等,通常不会被 gob 数据流传输的内容。
注意:由于 gob 可以被永久存储,一个优秀的设计必须保证被 GobEncoder 使用的编码必须在软件发展的过程中保持稳定。
举例来说,将版本号加入 GobEncode 的编码是一个有益的设计。
type GobEncoder interface {
// GobEncode 返回一个字节切片,该切片将会发送给某个 GobDecoder 解码,通常为相同的实际类型。
GobEncode() ([]byte, error)
}