binary.Read必须显式匹配写入时的字节序和结构体布局,否则会导致整数翻转、字段错位或io.ErrUnexpectedEOF;变长字段需分步读取,大文件应分块处理而非ReadAll。

binary.Read 必须匹配写入时的字节序和结构体布局
Go 的 binary.Read 不会自动推断字节序,也不校验字段对齐或大小是否与文件内容一致。一旦写入用的是 binary.LittleEndian,读取时也必须传同样的参数;否则整数会翻转、符号位错位,甚至读出负数或 0。结构体字段顺序、类型、大小必须和写入端完全一致——比如写入时是 struct{ A int32; B byte }(共 5 字节),读取时若改成 struct{ A int32; B uint16 },就会在第 5 字节后触发 io.ErrUnexpectedEOF。
常见错误现象:
- 读出的
int32值明显异常(如 1 变成 16777216),大概率是字节序反了 - 结构体中后面字段全为零,可能是前面某个字段读取失败导致 offset 偏移
- 反复出现
unexpected EOF,往往是因为文件长度不足或结构体定义多/少了一个字段
实操建议:
- 写入和读取两端统一用
binary.BigEndian(网络字节序),避免平台差异 - 用
binary.Size(&v)预先算出结构体预期字节数,再比对文件实际长度 - 读取前调用
file.Seek(0, io.SeekStart)确保从头开始,尤其在复用文件句柄时
变长字段不能直接放结构体里,得手动拆解
Go 结构体不支持运行时决定长度的数组字段,像 data [rec_len]byte 这种写法会编译报错 undefined: rec_len。你不能把“长度+数据”打包进一个结构体让 binary.Read 一次性解码。
立即学习“go语言免费学习笔记(深入)”;
正确做法是分两步:
- 先读固定头部(例如 4 字节:2 字节
RecLen+ 1 字节RecType+ 1 字节RecSub) - 用
binary.Read解析头部,拿到rec_len后再动态分配[]byte - 用
io.ReadFull(file, dataSlice)严格读满对应字节数,避免部分读取导致后续错位
关键点:别用 file.Read(),它可能只读到部分数据就返回;io.ReadFull() 才能保证要么读满、要么明确报错。
gob 适合 Go 内部序列化,但跨语言或版本升级要小心
gob 是 Go 自带的二进制序列化机制,写起来简单:encoder.Encode(info),读也直接:decoder.Decode(&info)。但它不是通用协议——Python、C++ 或旧版 Go 编译的程序基本无法解析,且不同 Go 版本间存在兼容性风险(比如结构体新增未导出字段可能导致解码失败)。
使用场景有限:
- 仅限 Go 进程间通信(如 RPC、本地缓存)
- 结构体字段名、包路径、类型签名都必须完全一致,否则
Decode会静默失败或 panic - 不支持自定义编码逻辑(比如加密、压缩、字段过滤)
如果将来可能对接其他语言,或需长期存档,优先选 encoding/binary + 显式协议定义,而不是 gob。
大文件别用 ReadAll,用 io.Reader 分块处理
io.ReadAll(file) 或 os.ReadFile() 会把整个文件加载进内存。对于几十 MB 以上的二进制文件,容易触发 OOM,尤其在嵌入式或资源受限环境。
更稳妥的方式是用 io.Reader 接口流式处理:
- 每次读固定大小(如 4KB 或结构体大小的整数倍),避免碎片
- 用
bytes.Buffer或预分配切片暂存,配合binary.Read解析 - 遇到损坏记录时可跳过(如检查 magic number 或长度字段合法性),不影响后续数据
真正容易被忽略的是错误分类:io.EOF 表示正常结束;io.ErrUnexpectedEOF 意味着当前记录不完整,应记录日志并终止;其他 I/O 错误(如磁盘故障)则需重试或告警。


















