应优先用gob存复杂结构体:它能完整保留Go类型信息、私有字段、指针关系和channel等,而JSON仅适用于跨语言、人可读场景;但需确保struct定义两端完全一致,否则解码失败。

Go 里把复杂结构体存到本地文件,关键不是“能不能”,而是“用什么序列化格式+怎么保真+怎么防坑”。直接 json.Marshal 或 gob.Encode 写文件看似简单,但字段丢失、类型错乱、跨版本读取失败这些事,往往在上线后才暴露。
什么时候该用 JSON,什么时候必须用 gob
JSON 适合人可读、跨语言、带前端交互的场景;gob 只能在 Go 进程间安全使用,但它能完整保留 struct 字段类型(包括 unexported 字段)、方法绑定、指针关系和 channel 等 —— 这些 JSON 根本不支持。
- 如果你要存的是配置、日志快照、API 响应缓存,选
json.Marshal+os.WriteFile,记得加json.MarshalIndent方便调试 - 如果你存的是带私有字段、嵌套 map[string]interface{}、或含函数/接口值的运行时状态(比如爬虫中间状态、session 缓存),必须用
gob.NewEncoder,且确保 struct 定义在读写两端完全一致 - 别混用:用
gob存的文件,不能用json.Unmarshal读;反过来也一样,会 panic
r.MultipartForm.File 之后,别用 image.Decode 再保存图片
上传图片后直接存本地,常见错误是先 image.Decode 再 jpeg.Encode。这不仅慢,还会破坏原始 EXIF、色彩空间、DCT 数据块,甚至让某些相机生成的 HEIC 转成无效 JPEG。
- 正确做法:拿到
multipart.File后,用io.Copy(dst, src)直接流式写入*os.File - 务必检查
dst.Close()和src.Close(),漏关会导致文件句柄泄漏,Linux 下撑满 1024 就卡死 - 如果需要校验 MIME 类型,用
http.DetectContentType检查前 512 字节,而不是只看后缀名
XML 结构体嵌套时,xml:"" 标签写错一个逗号就解析为空
encoding/xml 对标签语法极其敏感。比如 xml:"id,attr" 写成 xml:"id attr" 或 xml:"id,attribute",字段就永远为零值,且不报错。
立即学习“go语言免费学习笔记(深入)”;
- 属性必须用
,attr,子元素默认就是元素名,不用额外标记 - CDATA 内容要用
xml:",cdata",不能写成xml:"cdata" - 嵌套切片字段(如
Books []Book `xml:"book"`)必须确保 XML 中对应标签实际存在,空列表不会自动初始化,需手动判空 - 如果 XML 有命名空间(如
<rss xmlns:dc="http://purl.org/dc/elements/1.1/">),结构体字段标签得写成xml:"dc:title",且必须提前注册 namespace
gob 反序列化前,必须确保 struct 定义完全一致
gob 不是“存啥读啥”,它依赖 struct 的内存布局哈希。哪怕只是改了一个字段名大小写、加了个注释、或调整了字段顺序,gob.Decode 就会报 cannot decode type *main.User: incompatible type。
- 生产环境用 gob 存持久数据,必须冻结 struct 定义,或引入版本字段(如
Version int `gob:"version"`)做兼容判断 - 别把
time.Time直接塞进 gob —— 它底层是 struct,不同 Go 版本可能 layout 不同;统一转成UnixNano()存 int64,读时再time.Unix(0, nano) - gob 文件本身无 magic header,无法靠文件头判断格式,建议文件扩展名明确标出,比如
cache_v2.gob
最常被忽略的点:所有序列化操作都依赖 struct 字段是否 exported(首字母大写)。小写字母开头的字段,无论 JSON、XML 还是 gob,一律忽略 —— 这不是 bug,是 Go 的反射规则,改字段名比找序列化文档快得多。


















