Rust: Rust 常用集合:VecDeque、BTreeMap 与性能对比

最后更新:2026-08-26

VecDeque 是双端队列(double-ended queue),支持两端高效插入和删除;BTreeMap 是有序的键值映射,支持范围查询。它们是 Rust 集合库中"有特殊本领"的成员。

如果说 Vec 是"排队上车"(只能从队尾加入),那 VecDeque 就是"排队时有人插队"(前后都能进出)。如果说 HashMap 是"字典"(随机查找),那 BTreeMap 就是"通讯录"(按字母顺序排列,还能翻到某一页开始看)。


1. 你将学到


2. 概念图解

以下 Mermaid 对比图展示 VecDeque 双端队列与 BTreeMap B树映射的数据结构差异:

100%
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 实现:

RUST
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 插队 + 按编号查找

更糟的是,餐厅经理说:

用 Vec 完全搞不定这些需求。

(3) Rust 集合的方案

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) 四种集合结构对比

100%
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 双端队列——排队取号系统(难度 ⭐)

RUST
// ============================================
// 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);
    }
}

输出:

TEXT 📖 仅展示
当前排队人数: 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 有序键值存储——成绩排名(难度 ⭐⭐)

RUST
// ============================================
// 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());
}

输出:

TEXT 📖 仅展示
=== 成绩单(按姓名排序)===
  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 范围查询——日期区间查询(难度 ⭐⭐)

RUST
// ============================================
// 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);
}

输出:

TEXT 📖 仅展示
=== 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:四种集合性能对比(难度 ⭐⭐⭐)

RUST
// ============================================
// 四种集合性能对比:插入 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());
}

输出(示例,实际时间因机器而异):

TEXT 📖 仅展示
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:综合练习——任务调度系统(难度 ⭐⭐⭐)

RUST
// ============================================
// 综合示例: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);
}

输出:

TEXT 📖 仅展示
=== 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 条"历史记录。


❓ 常见问题

Q VecDeque 和 Vec 有什么区别?什么时候用 VecDeque?
A VecDeque 支持两端 O(1) 操作,Vec 只支持尾部 O(1)。
Q BTreeMap 和 HashMap 有什么区别?
A BTreeMap 有序,HashMap 无序。
Q LinkedList 在 Rust 中为什么很少用?
A LinkedList 的每个节点有额外指针开销,且缓存不友好。
Q BTreeMap 的 range 如何实现分页?
A 配合 next()nth() 实现游标分页。
Q 四种集合选型有没有简单口诀?
A 有。"尾部操作用 Vec,两端操作 VecDeque,无序查找 HashMap,有序查找 BTreeMap。"

📖 小节


📝 作业

  1. 难度 ⭐:用 VecDeque 实现一个"撤销历史"(undo history)。每次操作通过 push_back 记录,撤销时通过 pop_back 移除最近一次操作。模拟 3 次操作后撤销 2 次,打印每次操作的内容。
  2. 难度 ⭐⭐:用 BTreeMap 实现一个"学生成绩管理系统"。插入 5 名学生的姓名和成绩,按成绩从高到低打印排名(提示:BTreeMap 不能直接按值排序,需要把成绩作为键,姓名作为值,或者用 iter().rev())。
  3. 难度 ⭐⭐⭐:写一个函数 fn analyze_collections(data: &[i32]),接收一个整数切片,分别用 VecDeque、HashMap 和 BTreeMap 统计每个数字出现的次数,并比较三种方式的性能差异(用 std::time::Instant 计时)。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏