结构共享通过复用未变更子树节点、仅新建修改路径上的节点,将深层数组更新的内存分配从O(n)降至O(log n),显著降低GC压力并提升缓存局部性。

结构共享不是复制整个数据结构,而是复用未变更的子树节点,只新建被修改路径上的节点。它让深层数组(或嵌套结构)的更新操作从 O(n) 内存分配降为 O(log n) 或更低,尤其适合不可变数据场景。
为什么深层数组更新容易浪费内存
传统方式中,修改嵌套数组某一项(比如 arr[2][1][0] = 42)往往需要逐层拷贝:
- 先复制最外层数组 → 新数组 A′
- 再复制第 2 层子数组 → 新数组 B′
- 最后复制第 3 层并改值 → 新数组 C′
即使只改一个元素,也可能触发数十甚至上百个对象的重复分配,加剧 GC 压力,也破坏缓存局部性。
结构共享如何工作(以三层数组为例)
假设原始结构是:[[[1,2], [3]], [[4]]],要将 [0][1][0] 从 3 改为 99:
- 最内层
[3]变成[99]→ 新建一个数组 - 中间层
[[1,2], [3]]中,仅第一个子项不变,第二个子项已替换 → 新建该中间数组,但其[0]字段仍指向原[1,2] - 最外层数组只替换索引
0指向的新中间数组,其余项(如[1]对应的[[4]])完全复用
最终只有 3 个新对象被创建(而非全部 7 个),其余引用保持原样 —— 这就是结构共享的核心:按需新建,其余复用。
哪些语言/库支持开箱即用
并非所有语言原生支持,但可通过以下方式落地:
- Immutable.js(JS):底层使用哈希数组映射字典(HAMT),对数组和 Map 实现高效结构共享
- Scala 的 Vector:2^32 容量、32 叉树实现,单次更新仅新建约 5 个节点
-
Rust 的 im crate:提供
Vector、HashMap等共享结构,编译期零成本抽象 -
手动模拟(Go/Ruby/C#):用不可变结构体 + 显式
WithXxx()方法返回新实例(如知识库中Point.Move()示例),配合编译器逃逸分析避免堆分配
关键使用前提与注意事项
结构共享不是银弹,需满足几个条件才能真正省内存:
- 数据必须是逻辑不可变的(哪怕物理可变,也要约定不直接修改)
- 更新操作需走统一构造函数或 with 方法,禁止绕过封装直接赋值
- 避免频繁小更新拼成大变更 —— 连续 10 次单元素更新,可能不如一次批量构建新结构高效
- 对极小结构(如长度 ≤4 的数组),结构共享的指针开销可能反超直接拷贝,此时应退回到内联数组或栈分配










