encoding/binary仅适用于定长内存布局的数据结构,对含slice、string、map或指针的struct调用binary.write会panic,因其仅按unsafe.sizeof写入字段header而非真实数据。

encoding/binary 包不是通用序列化工具,它只适用于定长、可预测内存布局的数据结构。直接对含 []byte、string、map 或指针的结构体调用 binary.Write 会 panic,这点必须提前确认。
为什么 binary.Write 对 struct 失败却没报错?
它不校验字段是否变长,而是按 unsafe.Sizeof 逐字段写入内存原始字节 —— 如果结构体里有 slice 或 string,写进去的只是其 header(如 len/cap/ptr 地址),而非真实数据。反序列化时读出的 ptr 指向无效地址,运行时崩溃或读到垃圾值。
- 典型错误现象:
panic: reflect.Value.Interface: cannot return unaddressable value或读出的[]byte长度为 0 但 cap 很大 - 正确前提:结构体所有字段必须是定长基础类型(
uint32、[16]byte)、定长数组或嵌套定长 struct - 常见误用:把
type Msg struct { ID uint32; Body string }直接传给binary.Write——string字段只写 16 字节 header,Body 内容完全丢失
如何手动处理变长字段(如 string / []byte)?
必须拆解:先写长度,再写内容;反序列化时先读长度,再按长度读内容。不能依赖 binary.Read 一次性读整个 struct。
- 示例结构:
type Packet struct { Len uint16; Data [64]byte }可直接用binary.Write;但若要支持任意长度Data,就得改成Len uint16; Data []byte并手动编码 - 写法要点:
- 用
binary.BigEndian.PutUint16(buf[pos:], uint16(len(data)))写长度 - 再用
copy(buf[pos:], data)写内容,注意提前分配足够空间
- 用
- 读法要点:
- 先用
binary.BigEndian.Uint16(buf[:2])提取长度n - 再从
buf[2:2+n]截取内容,避免越界
- 先用
BigEndian 和 LittleEndian 怎么选?
端序选择取决于协议规范或对接方要求,不是性能问题。x86 CPU 默认小端,但网络协议(如 TCP/IP 头)和大多数跨语言场景强制大端 —— 别凭直觉选。
- 错误假设:“本地跑得通就用
LittleEndian” → 对接 Java/C# 服务时字节错乱,0x01020304被对方解析成0x04030201 - 安全做法:查清通信协议文档;无文档时默认用
binary.BigEndian(网络字节序) - 验证方法:用已知值(如
uint32(0x12345678))写入,hexdump 查看前 4 字节是否为12 34 56 78(大端)还是78 56 34 12(小端)
什么时候该换用 encoding/gob 或 json?
只要结构体含任何变长字段、需要跨进程复原 Go 类型、或开发调试阶段需人眼可读,encoding/binary 就不该是首选。
-
gob:Go 程序间通信首选,自动处理map、slice、string、指针,但输出不可读、不跨语言 -
json:调试友好、跨语言通用,但性能低、不保留 nil slice/map、浮点精度可能漂移 -
binary的合理位置:高性能内部协议(如游戏帧同步)、硬件寄存器映射、与 C 共享内存结构体 —— 此时你本就控制着两端内存布局
真正麻烦的从来不是怎么写那几行 binary.Write,而是当结构体加了一个新字段、或某天要对接外部系统时,才发现当初没预留长度字段、端序写反了、或者以为能自动序列化的 string 其实只写了 header。这些细节在第一次跑通时根本看不出问题。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











