集合与容器
📚 Rust 课程系列
集合(容器)是日常编程最常用的数据结构。Rust 标准库在 std::collections 下提供了一组覆盖常见场景的容器:动态数组 Vec、双端队列 VecDeque、哈希表 HashMap/HashSet、有序映射 BTreeMap/BTreeSet、链表 LinkedList 和二叉堆 BinaryHeap。本章逐一介绍它们的结构特点、典型用法、性能权衡与选用指南。各容器完整方法表见延伸阅读《Rust Containers》。
容器总览
| 类别 | 容器 | 特点 |
|---|---|---|
| 序列 | Vec<T>、VecDeque<T>、LinkedList<T> | 按插入顺序存放,可按位置或顺序访问 |
| 映射 | HashMap<K,V>、BTreeMap<K,V> | 键值对,按键查找 |
| 集合 | HashSet<T>、BTreeSet<T> | 仅键,去重/成员判断 |
| 堆 | BinaryHeap<T> | 优先队列,快速取最值 |
Vec、HashMap、BTreeMap 覆盖了绝大多数场景;其余容器在特定需求下才登场。
Vec<T>:动态数组
Vec<T> 是最常用的容器,在堆上存储连续的同类型元素,容量随需增长。
1 | |
容量与增长策略:Vec 内部维护「容量」(已分配槽位数)和「长度」(实际元素数)。当长度超过容量时,Vec 重新分配更大的内存(通常翻倍)并搬移旧元素。因此 push 是 O(1) 均摊成本,但偶发的重分配会带来一次 O(n) 拷贝。
💡 提示:当已知大小时使用
Vec::with_capacity(n)预分配以避免重复分配——这是Vec最常见的性能优化手段。
1 | |
访问与删除速查:
| 操作 | 方法 | 复杂度 | 备注 |
|---|---|---|---|
| 索引访问 | v[i] | O(1) | 越界 panic |
| 安全访问 | v.get(i) | O(1) | 返回 Option<&T> |
| 末尾删除 | pop() | O(1) | 返回 Option<T> |
| 中间删除 | remove(i) | O(n) | 后续元素前移 |
| 中间删除 | swap_remove(i) | O(1) | 与末尾交换后 pop,不保序 |
⚠️ 注意:
swap_remove虽然是 O(1),但会打乱元素顺序。需要保序时只能用remove。
🔄 对比:Rust 的
Vec≈ C++ 的std::vector/ Go 的slice/ Java 的ArrayList,但 Rust 的所有权系统保证不会出现悬垂引用——持有&v[i]时编译器会阻止你push新元素(因为 push 可能触发重分配使引用失效)。
VecDeque<T>:双端队列
VecDeque 基于环形缓冲(ring buffer),两端插入/删除都是 O(1) 均摊。适合队列、滑动窗口等场景:
1 | |
💡 提示:需要 FIFO 队列时优先选
VecDeque而非Vec——Vec的remove(0)是 O(n),而VecDeque的pop_front是 O(1)。
HashMap<K, V>:哈希表
HashMap 用哈希表实现键值映射,平均 O(1) 的查找/插入/删除,但无序。
1 | |
⚠️ 注意:键类型必须实现
Hash+Eq。所有整数、String/&str、元组(元素均实现Hash+Eq)等自动满足;自定义类型需 derive 或手动实现。
哈希算法(进阶)
默认使用 SipHash-1-3,抗哈希碰撞攻击(防止恶意输入触发最坏 O(n) 退化),但相对慢。若性能敏感且键可信,可换更快的 hasher:
1 | |
🔄 对比:Go 的 map 和 Java 的 HashMap 也使用类似策略,但 Rust 默认选择安全优先的 SipHash,而 C++
std::unordered_map默认哈希函数不抗碰撞攻击。
Entry API
entry API 是 HashMap 最惯用的技巧——一次查找完成「存在则读,不存在则写」,避免重复查找:
1 | |
💡 提示:
or_insert(v)总会求值v(即使键已存在);若默认值计算昂贵,用or_insert_with(|| expensive())只在需要时才计算。值为Default时可用or_default()更简洁。
BTreeMap<K, V>:有序映射
BTreeMap 基于 B 树,按键有序排列,查找/插入/删除为 O(log n),支持按范围遍历。当需要按键排序或范围查询时用它替代 HashMap:
1 | |
⚠️ 注意:键类型必须实现
Ord。BTreeMap的插入/删除比HashMap慢(O(log n) vs O(1) 均摊),但遍历有序且无哈希开销。不需要顺序时优先用HashMap。
🔄 对比:
BTreeMap≈ C++ 的std::map/ Java 的TreeMap,但 Rust 的 B 树节点通常比 C++ 红黑树节点缓存更友好(B 树节点存多个元素)。
HashSet 与 BTreeSet
HashSet<T> 和 BTreeSet<T> 分别是 HashMap<T, ()> 和 BTreeMap<T, ()> 的集合版本,只存键,用于去重与成员判断:
1 | |
💡 提示:集合运算返回的是引用的迭代器,需要
.cloned().collect()才能得到新的HashSet。BTreeSet同样支持intersection/union/difference/symmetric_difference,且结果保持有序。
LinkedList<T>:双向链表(慎用)
LinkedList<T> 两端 O(1) 操作,但缓存不友好、随机访问 O(n)。在 Rust 中绝大多数「想用链表」的场景用 Vec/VecDeque 更快更简单。
⚠️ 注意:
LinkedList主要在需要频繁从中间拆分/拼接(split_off/append)时才考虑。官方也建议谨慎使用——Rust 的所有权模型使链表操作比 C/C++ 更繁琐(需要 unsafe 或Rc<RefCell<>>才能实现双向引用)。
🔄 对比:C++ 的
std::list在中间插入 O(1) 常被使用,但 Rust 的LinkedList因所有权限制几乎无法直接操作中间节点,实用性远低于 C++ 版本。
BinaryHeap<T>:二叉堆
BinaryHeap<T> 是最大二叉堆,常作优先队列:peek O(1),push/pop O(log n)。
1 | |
要最小堆或自定义顺序,用 Reverse 包装或实现自定义 Ord:
1 | |
💡 提示:
BinaryHeap要求元素实现Ord。自定义优先级时,推荐用Reverse包装(零开销)而非实现反向Ord——后者容易在别处引起混淆。
性能对比
| 容器 | 索引/查找 | 插入/删除 | 备注 |
|---|---|---|---|
Vec<T> | O(1) 索引 | 末尾 O(1) 均摊;中间 O(n) | 连续内存,缓存友好 |
VecDeque<T> | O(1) 索引 | 两端 O(1) 均摊;中间 O(n) | 环形缓冲 |
HashMap<K,V> | O(1) 均摊 | O(1) 均摊 | 无序,需 Hash + Eq |
BTreeMap<K,V> | O(log n) | O(log n) | 有序,需 Ord |
HashSet/BTreeSet | 同对应 Map | 同对应 Map | 无值 |
LinkedList<T> | O(n) | 两端 O(1);中间 O(1)(已知游标) | 缓存不友好,慎用 |
BinaryHeap<T> | peek O(1) | push/pop O(log n) | 最大堆 |
选用指南
| 需求 | 推荐 |
|---|---|
| 一组同类型元素,按下标访问 | Vec |
| 两端频繁增删(队列/滑动窗口) | VecDeque |
| 键值映射、查找为主、不关心顺序 | HashMap |
| 键值映射、需要按键排序或范围查询 | BTreeMap |
| 去重 / 成员判断 / 集合运算 | HashSet(无序)或 BTreeSet(有序) |
| 取最值 / 优先队列 | BinaryHeap |
| 频繁从中间拆分/拼接 | LinkedList(默认优先考虑 Vec) |
并发集合
标准库的集合不是线程安全的——它们没有实现内部同步,跨线程共享可变集合需要外部锁。
1 | |
⚠️ 注意:
Arc<Mutex<HashMap<...>>>简单但锁粒度粗,竞争激烈时性能差。读多写少可用Arc<RwLock<...>>,但注意RwLock可能导致写饥饿。
🔬 进阶:更高吞吐可使用第三方并发数据结构如
dashmap,它采用分片锁(sharded locking)显著降低锁竞争:
1 | |
🔄 对比:Java 有
ConcurrentHashMap、Go 有sync.Map,Rust 标准库没有内置并发 map——这是 Rust 的设计哲学:零成本抽象意味着不为单线程场景支付同步开销,并发需求交给第三方 crate。
与迭代器配合
集合与迭代器适配器天然契合,collect() 可把迭代器结果收集回任意实现了 FromIterator 的集合:
1 | |
💡 提示:
collect()需要类型标注让编译器推断目标容器。当推断歧义时,用「turbofish」语法:v.iter().cloned().collect::<HashSet<_>>()。
迭代器的完整方法与惰性求值细节见《智能指针、迭代器与闭包》。
延伸阅读:Rust Containers(每个容器的创建方法、常用方法表、性能特点与适用场景的完整参考)。

