Go json.RawMessage 延迟解析时如何避免共享底层字节
用 json.RawMessage 做延迟解析时,最容易误判的是“类型名像字节切片,所以赋值一定安全”。结论很明确:通过 json.Unmarshal 写入 RawMessage 时,标准库会复制输入;但把已有的 []byte 直接转换成 json.RawMessage,或者把一个 RawMessage 直接赋给另一个变量,仍可能共享底层数组。只要原缓冲区还会复用,就应在跨出它的生命周期前复制。
把 RawMessage 留在当前解码对象里通常不需要额外复制;把它交给队列、缓存或另一个 goroutine 时,使用 append([]byte(nil), src...) 建立独立所有权,再做延迟解析。
UnmarshalJSON的契约是把输入保存为副本。json.RawMessage(src)只是切片转换,不是深复制。- 跨缓冲区、队列或 goroutine 传递时,在边界处 clone 一次。
先分清标准库复制和手动共享
RawMessage 本质上是 []byte 的命名类型,价值在于先保留一段合法 JSON,等知道业务类型后再调用 json.Unmarshal。官方实现的 UnmarshalJSON 会用 append 把输入复制到接收者,因此下面这种结构可以直接保存原始字段:
type Envelope struct {
Kind string `json:"kind"`
Payload json.RawMessage `json:"payload"` // 先保存原始 JSON,稍后按 Kind 解析
}
var e Envelope
if err := json.Unmarshal(data, &e); err != nil {
return err // 输入结构不合法时,不把半成品交给后续业务
}
// e.Payload 已经拥有自己的字节副本,可以脱离 data 延迟解析。
危险点在手动提取路径。view := json.RawMessage(data) 不会逐字节复制,view 与 data 仍指向同一块底层数组。若读取器下一轮把新消息写回 data,保存的 view 就可能被悄悄改写。

把复制放在跨生命周期的边界
复制不应到处散落,而应放在“数据即将离开输入缓冲区”的那个位置。一个足够通用的 helper 如下:
func cloneRaw(src []byte) json.RawMessage {
// append 到 nil 切片会分配新底层数组,避免与 src 共享存储
dst := append([]byte(nil), src...)
return json.RawMessage(dst)
}
func retainForQueue(frame []byte) json.RawMessage {
// 入队后由其他 goroutine 消费,所以这里必须先取得所有权
return cloneRaw(frame)
}
如果项目最低版本允许,也可以用 bytes.Clone 表达同样的意图。关键不是 helper 的名字,而是让调用者一眼看出返回值不再依赖输入缓冲区。对空切片要保留语义:append([]byte(nil), nil...) 得到 nil,后续 Marshal 时会按 RawMessage 的规则表现为 JSON null;如果业务需要空数组,应明确传入 [],不要用 clone 偷换语义。

延迟解析和并发消费时检查四个节点
第一,解析前不要修改 RawMessage 的内容。它可以作为输入交给 json.Unmarshal,但不要一边交给消费者一边对同一底层数组做原地清洗。第二,切片赋值只是复制三元组(指针、长度、容量),例如 saved := current 不会产生独立数据;要独立保存就调用 cloneRaw(saved)。
第三,交给 goroutine 的不是“变量”,而是它引用的字节。即使外层结构体没有再变化,只要来源 buffer 会被复用,仍然可能发生数据竞争或内容漂移。第四,重新编码时 RawMessage.MarshalJSON 会直接返回自身字节,因此它也不替你创建并发安全的快照;需要快照的场景应在进入共享区域前完成复制。
| 来源 | 能否直接长期保存 | 建议 |
|---|---|---|
json.Unmarshal 写入 RawMessage | 可以 | 标准库已复制输入 |
json.RawMessage(src) | 通常不可以 | 先 clone,再入队或缓存 |
| RawMessage 直接赋值 | 不可以 | 按需要复制底层字节 |
| 同一 goroutine 内立即解析 | 视来源而定 | 不跨生命周期时可避免多余复制 |
用一张清单固定项目里的判断
可以把下面四个问题放进 code review:这段字节来自标准库解码还是复用 buffer?RawMessage 会不会进入队列、缓存或 goroutine?持有期间是否有人会原地写入源切片?下游是否需要一个不可变快照?只要前三问出现“会”,或者最后一问是“需要”,就在边界处复制一次。
实践中不要为了“保险”在每次 json.Unmarshal 前后都复制。那会制造额外分配,却没有增加安全性。更稳妥的路线是:标准库解码结果直接使用;手动切片转换先 clone;跨异步或缓存边界时明确转移所有权;完成快照后不再修改其字节。
相关问题
RawMessage 延迟解析是不是一定比直接解析更快?
不一定。它适合先根据类型字段选择目标结构,或只处理部分消息;如果最终所有字段都会解析,延迟本身不会消除解析成本。
RawMessage 和 []byte 直接比较有什么问题?
两者都是字节序列,不能用 == 比较;需要内容比较时使用 bytes.Equal,并先确认双方是否代表同一种 JSON 形式。
复制一次后还能修改 RawMessage 吗?
可以,但应把它视为当前持有者的私有数据。不要在它已经交给其他 goroutine 或编码器使用时原地修改,否则仍会产生数据竞争。
Transformers generate 如何用 stopping criteria 停在自定义标记
- 上一篇
- Transformers generate 如何用 stopping criteria 停在自定义标记
- 下一篇
- Python 3.14 free-threaded 构建如何确认解释器模式
-
- Golang · Go教程 | 32分钟前 |
- Go csv.Reader.LazyQuotes 开启后哪些坏格式仍会失败
- 327浏览 收藏
-
- Golang · Go教程 | 48分钟前 | 文件读取 · Go教程 · CSV解析 · encoding/csv · csv.Reader · Go csv.Reader Comment Go CSV 跳过注释行 encoding/csv 注释字符 csv.Reader 前导空白
- Go csv.Reader Comment 如何跳过输入中的注释行
- 215浏览 收藏
-
- Golang · Go教程 | 56分钟前 |
- Go json.Number 读取大整数时怎样避免浮点精度丢失
- 454浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go json.Decoder 输入未知字段如何兼容灰度客户端
- 372浏览 收藏
-
- Golang · Go教程 | 1小时前 | go · HTTP路由 · http.ServeMux · Go http.ServeMux 方法模式 路径匹配
- Go http.ServeMux 方法模式如何同时限制路径和请求方法
- 217浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go http.Client 如何限制重定向次数
- 147浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Go url.Values.Encode 如何保证签名参数排序稳定
- 204浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Go url.URL.JoinPath 处理双斜杠时结果为什么改变
- 440浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Go context.Cause 如何区分超时和业务主动取消
- 276浏览 收藏
-
- Golang · Go教程 | 3小时前 | Context · 并发控制 · Go教程 · 请求生命周期 · Go Deadline context.Context 取消信号 context.WithoutCancel
- Go context.WithoutCancel 继承值但不继承取消信号吗
- 374浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 26次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 130次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 57次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 22次使用
-
- OpenCompass
- OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
- 80次使用
-
- Java 性能优化上线清单:从定位、改造到灰度发布
- 2026-06-11 860浏览
-
- Spring Boot 压测验证:Gatling、JMeter 与性能回归门禁
- 2026-06-11 843浏览
-
- Java NMT 非堆内存排查:Direct Buffer、线程栈与 Metaspace 分析
- 2026-06-11 826浏览
-
- Spring Boot 容器内存优化:JVM 堆、非堆与 MaxRAMPercentage
- 2026-06-11 809浏览
-
- Tomcat 连接与线程参数调优:maxThreads、acceptCount 与 KeepAlive
- 2026-06-11 792浏览

