Rust: Rust 常用集合:VecDeque、BTreeMap 与性能对比
最后更新:2026-08-26
VecDeque 是双端队列(double-ended queue),支持两端高效插入和删除;BTreeMap 是有序的键值映射,支持范围查询。它们是 Rust 集合库中"有特殊本领"的成员。
如果说 Vec 是"排队上车"(只能从队尾加入),那 VecDeque 就是"排队时有人插队"(前后都能进出)。如果说 HashMap 是"字典"(随机查找),那 BTreeMap 就是"通讯录"(按字母顺序排列,还能翻到某一页开始看)。
1. 你将学到
- VecDeque 双端队列的
push_front/pop_back操作 - BTreeMap 有序键值存储与范围查询
range - LinkedList 的适用场景与限制
- 四种集合(Vec / VecDeque / HashMap / BTreeMap)的性能对比
- 集合选型策略:根据场景选择正确的数据结构
2. 概念图解
以下 Mermaid 对比图展示 VecDeque 双端队列与 BTreeMap B树映射的数据结构差异:
graph LR
subgraph VecDeque["VecDeque 双端队列"]
direction LR
V1["pop_front O(1)"] --> V2["元素1"]
V2 --> V3["元素2"]
V3 --> V4["..."]
V4 --> V5["元素N"]
V5 --> V6["push_back O(1)"]
V7["push_front O(1)"] -.-> V2
end
subgraph BTreeMap["BTreeMap B树有序映射"]
direction TB
B0["根节点"] --> B1["左分支<br/>键: A-M"]
B0 --> B2["右分支<br/>键: N-Z"]
B1 --> B3["(Alice, 95)"]
B1 --> B4["(Bob, 72)"]
B2 --> B5["(Charlie, 87)"]
B2 --> B6["(David, 91)"]
end
VecDeque -.-> |数据结构对比| BTreeMap
3. 一个排队取号系统的故事
(1) 痛苦:用 Vec 模拟排队,但插队和离队都很慢
Tom (Tom) 在开发餐厅排队取号系统。顾客到店取号,服务员叫号入座。
最开始他用 Vec 实现:
let mut queue = Vec::new();
queue.push("Customer A"); // A 取号,排在队尾
queue.push("Customer B"); // B 取号,排在队尾
queue.push("Customer C"); // C 取号,排在队尾
// 叫号入座:从队头移除
let first = queue.remove(0); // O(n) —— 后面的所有元素都要前移!
println!("{} 入座", first);
Vec 的
remove(0)是 O(n) 操作——移除第一个元素后,剩下所有元素都要向前移动一个位置。如果有 10000 人在排队,每次叫号就要移动 9999 个元素。这显然不能用于生产系统。
(2) 更复杂的需求:VIP 插队 + 按编号查找
更糟的是,餐厅经理说:
- VIP 客户可以插队到队首
- 客服需要按取号编号快速查找顾客信息
- 有时需要查询"下午 2 点到 3 点之间取号的顾客"
用 Vec 完全搞不定这些需求。
(3) Rust 集合的方案
use std::collections::VecDeque;
use std::collections::BTreeMap;
fn main() {
// VecDeque:前后都能进出
let mut queue = VecDeque::new();
queue.push_back("Customer A"); // 队尾进入
queue.push_back("Customer B");
queue.push_front("VIP"); // VIP 从队首插队!
println!("下一个入座: {}", queue.pop_front().unwrap()); // VIP
println!("再下一个: {}", queue.pop_front().unwrap()); // Customer A
// BTreeMap:有序键值存储
let mut customers = BTreeMap::new();
customers.insert(1001, "Alice");
customers.insert(1003, "Bob");
customers.insert(1002, "Charlie");
// 自动按键排序输出
for (id, name) in &customers {
println!("取号 #{}: {}", id, name);
}
// 输出: #1001: Alice, #1002: Charlie, #1003: Bob
}
VecDeque 的两端操作都是 O(1),BTreeMap 按键自动排序并支持范围查询。前者解决"排队"问题,后者解决"有序查找"问题。
4. 核心概念
(1) 四种集合结构对比
graph TB
A[Rust 集合库] --> B[Vec: 连续数组]
A --> C[VecDeque: 双端队列]
A --> D[HashMap: 哈希表]
A --> E[BTreeMap: B 树有序映射]
B --> F["push / pop: O(1) 均摊"]
B --> G["insert(0) / remove(0): O(n)"]
C --> H["push_front / pop_front: O(1)"]
C --> I["push_back / pop_back: O(1)"]
D --> J["插入 / 查找: O(1) 均摊"]
D --> K["无序 —— 没有顺序保证"]
E --> L["插入 / 查找: O(log n)"]
E --> M["有序 —— 支持范围查询 range"]
(2) 四种集合性能对比
| 操作 | Vec | VecDeque | HashMap | BTreeMap |
|---|---|---|---|---|
| 尾部插入 | O(1) 均摊 | O(1) | O(1) 均摊 | O(log n) |
| 头部插入 | O(n) | O(1) | — | — |
| 按索引访问 | O(1) | O(1) | — | — |
| 按键查找 | — | — | O(1) 均摊 | O(log n) |
| 范围查询 | 不支持 | 不支持 | 不支持 | O(log n + k) |
| 有序遍历 | 需排序 | 需排序 | 无序 | O(n) 有序 |
| 内存开销 | 低 | 低 | 中 | 中 |
(3) 选型策略
| 场景 | 推荐集合 | 理由 |
|---|---|---|
| 排队 / 缓冲区(两端操作) | VecDeque | 两端 O(1),没有比它更合适的 |
| 需要按键排序遍历 | BTreeMap | 自动排序,无需额外排序步骤 |
| 需要范围查询 | BTreeMap | 唯一支持范围查询的集合 |
| 只需要尾部操作 | Vec | 更简单,内存更紧凑 |
| 只需要快速键值查找 | HashMap | O(1) 查找,比 BTreeMap 快 |
| 频繁插入删除中间元素 | LinkedList | 理论 O(1),但实际很少用 |
(4) VecDeque 与 BTreeMap 常用方法速查
| 集合 | 方法 | 说明 | 时间复杂度 |
|---|---|---|---|
| VecDeque | push_front(val) |
队首插入 | O(1) |
| VecDeque | push_back(val) |
队尾插入 | O(1) |
| VecDeque | pop_front() |
队首弹出 | O(1) |
| VecDeque | pop_back() |
队尾弹出 | O(1) |
| VecDeque | front() / back() |
查看首尾元素 | O(1) |
| VecDeque | len() / is_empty() |
长度查询 | O(1) |
| BTreeMap | insert(k, v) |
插入键值对 | O(log n) |
| BTreeMap | get(&k) |
按键查找 | O(log n) |
| BTreeMap | remove(&k) |
按键删除 | O(log n) |
| BTreeMap | range(start..=end) |
范围查询 | O(log n + k) |
| BTreeMap | first_key_value() |
最小键值对 | O(log n) |
| BTreeMap | last_key_value() |
最大键值对 | O(log n) |
5. 集合示例
▶ 示例 1:VecDeque 双端队列——排队取号系统(难度 ⭐)
// ============================================
// VecDeque:双端队列的基本操作
// ============================================
use std::collections::VecDeque;
fn main() {
let mut queue: VecDeque<&str> = VecDeque::new();
// 普通顾客从队尾进入
queue.push_back("Alice");
queue.push_back("Bob");
queue.push_back("Charlie");
// VIP 顾客从队首插队
queue.push_front("VIP-David");
queue.push_front("VIP-Eve");
println!("当前排队人数: {}", queue.len());
println!("队首: {:?}, 队尾: {:?}", queue.front(), queue.back());
// 叫号入座:从队首移除
println!("\n叫号顺序:");
while let Some(customer) = queue.pop_front() {
println!(" {} 入座", customer);
}
}
输出:
当前排队人数: 5
队首: Some("VIP-Eve"), 队尾: Some("Charlie")
叫号顺序:
VIP-Eve 入座
VIP-David 入座
Alice 入座
Bob 入座
Charlie 入座
push_front()和push_back()都是 O(1) 操作。front()和back()返回队首/队尾的引用而不移除元素。pop_front()移除并返回队首元素。
▶ 示例 2:BTreeMap 有序键值存储——成绩排名(难度 ⭐⭐)
// ============================================
// BTreeMap:按键自动排序
// ============================================
use std::collections::BTreeMap;
fn main() {
let mut scores = BTreeMap::new();
scores.insert("Charlie", 87);
scores.insert("Alice", 95);
scores.insert("Bob", 72);
scores.insert("David", 91);
// BTreeMap 按键(字母顺序)自动排序
println!("=== 成绩单(按姓名排序)===");
for (name, score) in &scores {
println!(" {}: {}", name, score);
}
// 按键查找
println!("\nAlice 的成绩: {}", scores.get("Alice").unwrap());
// 检查是否存在
if scores.contains_key("Eve") {
println!("Eve 的成绩存在");
} else {
println!("Eve 还没有成绩");
}
// 获取第一个和最后一个条目
println!("\n第一个: {:?}", scores.first_key_value());
println!("最后一个: {:?}", scores.last_key_value());
}
输出:
=== 成绩单(按姓名排序)===
Alice: 95
Bob: 72
Charlie: 87
David: 91
Alice 的成绩: 95
Eve 还没有成绩
第一个: Some(("Alice", 95))
最后一个: Some(("David", 91))
BTreeMap 按键的
Ord顺序自动排序。String 的默认顺序是字典序,所以 "Alice" 在最前,"David" 在最后。first_key_value()和last_key_value()提供高效的首尾访问。
▶ 示例 3:BTreeMap 范围查询——日期区间查询(难度 ⭐⭐)
// ============================================
// BTreeMap 范围查询:range 方法
// ============================================
use std::collections::BTreeMap;
fn main() {
// 模拟订单系统:日期 -> 订单金额
let mut orders = BTreeMap::new();
orders.insert("2026-07-01", 120);
orders.insert("2026-07-03", 85);
orders.insert("2026-07-05", 200);
orders.insert("2026-07-07", 150);
orders.insert("2026-07-10", 95);
// 查询 7 月 3 日到 7 月 7 日之间的订单(包含两端)
println!("=== 7月3日 ~ 7月7日 订单 ===");
for (date, amount) in orders.range("2026-07-03"..="2026-07-07") {
println!(" {}: ${}", date, amount);
}
// 查询所有 >= 7月5日的订单
println!("\n=== 7月5日及之后 订单 ===");
for (date, amount) in orders.range("2026-07-05"..) {
println!(" {}: ${}", date, amount);
}
// 汇总金额
let total: i32 = orders.range("2026-07-03"..="2026-07-07")
.map(|(_, amount)| amount)
.sum();
println!("\n7月3日~7月7日总金额: ${}", total);
}
输出:
=== 7月3日 ~ 7月7日 订单 ===
2026-07-03: $85
2026-07-05: $200
2026-07-07: $150
=== 7月5日及之后 订单 ===
2026-07-05: $200
2026-07-07: $150
2026-07-10: $95
7月3日~7月7日总金额: $435
range()是 BTreeMap 独有的方法。它接受一个 Range 表达式(..开区间,..=闭区间,start..半开区间),返回一个迭代器。这是 HashMap 做不到的。
▶ 示例 4:四种集合性能对比(难度 ⭐⭐⭐)
// ============================================
// 四种集合性能对比:插入 10000 个元素
// ============================================
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::time::Instant;
fn main() {
let n = 10_000;
// Vec 尾部插入
let start = Instant::now();
let mut vec = Vec::new();
for i in 0..n {
vec.push(i);
}
println!("Vec 尾部插入: {:?}", start.elapsed());
// VecDeque 尾部插入
let start = Instant::now();
let mut deque = VecDeque::new();
for i in 0..n {
deque.push_back(i);
}
println!("VecDeque 尾部插入: {:?}", start.elapsed());
// HashMap 插入
let start = Instant::now();
let mut hmap = HashMap::new();
for i in 0..n {
hmap.insert(i, i);
}
println!("HashMap 插入: {:?}", start.elapsed());
// BTreeMap 插入
let start = Instant::now();
let mut bmap = BTreeMap::new();
for i in 0..n {
bmap.insert(i, i);
}
println!("BTreeMap 插入: {:?}", start.elapsed());
// 查找性能对比
println!("\n--- 查找性能 ---");
let start = Instant::now();
let _ = vec.contains(&9999);
println!("Vec 线性查找: {:?}", start.elapsed());
let start = Instant::now();
let _ = hmap.get(&9999);
println!("HashMap 查找: {:?}", start.elapsed());
let start = Instant::now();
let _ = bmap.get(&9999);
println!("BTreeMap 查找: {:?}", start.elapsed());
}
输出(示例,实际时间因机器而异):
Vec 尾部插入: 78.2µs
VecDeque 尾部插入: 82.1µs
HashMap 插入: 1.2ms
BTreeMap 插入: 2.8ms
--- 查找性能 ---
Vec 线性查找: 42.5µs
HashMap 查找: 138ns
BTreeMap 查找: 312ns
从性能数据可以看出:HashMap 查找最快(O(1)),BTreeMap 次之(O(log n)),Vec 线性查找最慢(O(n))。插入方面,Vec 和 VecDeque 的尾部插入最快,BTreeMap 最慢。这验证了"没有银弹"——选择集合要根据具体场景。
▶ 示例 5:综合练习——任务调度系统(难度 ⭐⭐⭐)
// ============================================
// 综合示例:VecDeque 队列 + BTreeMap 优先级
// ============================================
use std::collections::{VecDeque, BTreeMap};
#[derive(Debug, Clone)]
struct Task {
id: u32,
name: String,
priority: u8,
}
fn main() {
let mut queue: VecDeque<Task> = VecDeque::new();
queue.push_back(Task { id: 1, name: "Check email".into(), priority: 3 });
queue.push_back(Task { id: 2, name: "Fix bug #42".into(), priority: 1 });
queue.push_back(Task { id: 3, name: "Write report".into(), priority: 2 });
queue.push_back(Task { id: 4, name: "Urgent deploy".into(), priority: 0 });
println!("=== FIFO 队列处理 ===");
while let Some(task) = queue.pop_front() {
println!("[P{}] #{}: {}", task.priority, task.id, task.name);
}
let mut priority_map: BTreeMap<u8, Vec<String>> = BTreeMap::new();
let tasks = vec![
("Check email", 3u8), ("Fix bug #42", 1), ("Write report", 2),
("Urgent deploy", 0), ("Code review", 1), ("Team meeting", 3),
("Security patch", 0), ("Update docs", 2),
];
for (name, pri) in tasks {
priority_map.entry(pri).or_insert_with(Vec::new).push(name.to_string());
}
println!("\n=== 按优先级排序 ===");
for (pri, task_list) in &priority_map {
println!("P{}: {:?}", pri, task_list);
}
println!("\n=== 高优先级任务 (P0-P1) ===");
for (pri, task_list) in priority_map.range(0..=1) {
println!("P{}: {:?}", pri, task_list);
}
let mut history: VecDeque<String> = VecDeque::with_capacity(3);
for i in 0..5 {
history.push_back(format!("task_{}", i));
if history.len() > 3 {
history.pop_front();
}
}
println!("\n最近 3 条历史: {:?}", history);
}
输出:
=== FIFO 队列处理 ===
[P3] #1: Check email
[P1] #2: Fix bug #42
[P2] #3: Write report
[P0] #4: Urgent deploy
=== 按优先级排序 ===
P0: ["Urgent deploy", "Security patch"]
P1: ["Fix bug #42", "Code review"]
P2: ["Write report", "Update docs"]
P3: ["Check email", "Team meeting"]
=== 高优先级任务 (P0-P1) ===
P0: ["Urgent deploy", "Security patch"]
P1: ["Fix bug #42", "Code review"]
最近 3 条历史: ["task_2", "task_3", "task_4"]
VecDeque 做 FIFO 队列(
push_back+pop_front);BTreeMap 按优先级分组并自动排序;range(0..=1)只查看高优先级任务;VecDeque 的固定容量实现"最近 N 条"历史记录。
❓ 常见问题
next() 和 nth() 实现游标分页。📖 小节
- VecDeque 双端队列:
push_front/push_back/pop_front/pop_back都是 O(1) - BTreeMap 有序映射:按键自动排序,支持
range()范围查询 - LinkedList 在 Rust 中很少用——缓存不友好,实际性能不如 VecDeque
- HashMap 查找 O(1) 但无序,BTreeMap 查找 O(log n) 但有序
- 集合选型策略:根据操作模式(尾部/两端/查找/范围)选择正确的集合
- VecDeque 和 BTreeMap 是 std::collections 中被低估的宝藏——很多场景它们比 Vec 和 HashMap 更合适
📝 作业
- 难度 ⭐:用 VecDeque 实现一个"撤销历史"(undo history)。每次操作通过
push_back记录,撤销时通过pop_back移除最近一次操作。模拟 3 次操作后撤销 2 次,打印每次操作的内容。 - 难度 ⭐⭐:用 BTreeMap 实现一个"学生成绩管理系统"。插入 5 名学生的姓名和成绩,按成绩从高到低打印排名(提示:BTreeMap 不能直接按值排序,需要把成绩作为键,姓名作为值,或者用
iter().rev())。 - 难度 ⭐⭐⭐:写一个函数
fn analyze_collections(data: &[i32]),接收一个整数切片,分别用 VecDeque、HashMap 和 BTreeMap 统计每个数字出现的次数,并比较三种方式的性能差异(用std::time::Instant计时)。