首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Rust训练----从零掌握 Rust 集合类型使用技巧

Rust训练----从零掌握 Rust 集合类型使用技巧

原创
作者头像
用户12339161
修改2026-05-29 16:50:08
修改2026-05-29 16:50:08
420
举报

Rust 的集合类型是日常开发中最高频的数据结构,但很多人只会 pushget。本文从零梳理 Rust 核心集合类型的选用逻辑、实战技巧与常见坑点,帮你真正"掌握"而非"会用"。


一、先搞清:Rust 集合类型到底有哪些?

Rust 标准库提供的集合并不多,但每一个都有明确的设计意图:

类型

底层结构

有序?

适用场景

Vec<T>

动态数组

✅ 按插入顺序

绝大多数默认选择

VecDeque<T>

环形队列

两端频繁增删

LinkedList<T>

双向链表

中间频繁插入/删除

HashMap<K,V>

哈希表

按 Key 快速查找

BTreeMap<K,V>

红黑树

✅ 按 Key 排序

需要有序遍历

HashSet<T>

哈希表

去重 + 快速判断存在

BTreeSet<T>

红黑树

✅ 排序去重

需要有序去重

BinaryHeap<T>

二叉堆

优先级队列

选型的第一原则:不知道选什么,就选 Vec HashMap,覆盖 80% 场景。


二、Vec:远不止 push_back

Vec 是 Rust 中使用频率最高的类型,但很多人只用了它 20% 的能力。

技巧 1:用 with_capacity 避免重复分配

代码语言:javascript
复制
rust// ❌ 每次 push 都可能触发扩容 + 拷贝
let mut v = Vec::new();
for i in 0..10000 {
    v.push(i);
}

// ✅ 提前分配,零额外开销
let mut v = Vec::with_capacity(10000);
for i in 0..10000 {
    v.push(i);
}

with_capacity 不改变 len,只预分配内存。这在已知数据量时是必用优化。

技巧 2:extend 比循环 push 快得多

代码语言:javascript
复制
rustlet mut v = vec![1, 2, 3];
let more = vec![4, 5, 6];

// ✅ 一次性extend,内部会用 memcpy 优化
v.extend(more);

// ❌ 逐个 push,无法批量优化
for x in more {
    v.push(x);
}

技巧 3:drain 是被严重低估的方法

代码语言:javascript
复制
rustlet mut v = vec![1, 2, 3, 4, 5];

// 删除索引 1..3 的元素,并拿到被删除的部分
let drained = v.drain(1..3); 
// v 现在是 [1, 4, 5]
// drained 是 [2, 3]

// 常见用法:批量删除满足条件的元素
v.retain(|&x| x % 2 == 0); // 只保留偶数

drain 的性能远优于 remove + 循环,因为它是一次性内存移动,而不是逐个前移。


三、HashMap:90% 的人踩过这些坑

坑 1:直接用 [] 取值会 panic

代码语言:javascript
复制
rustlet mut map = HashMap::new();
map.insert("key", 42);

// ❌ 如果 key 不存在,直接 panic
let v = map["key"];

// ✅ 用 get,返回 Option
if let Some(&v) = map.get("key") {
    println!("{}", v);
}

// ✅ 用 entry API,原子性地"获取或插入"
let v = map.entry("key").or_insert(0);

坑 2:遍历中修改 HashMap

代码语言:javascript
复制
rustlet mut map = HashMap::new();
map.insert(1, "a");
map.insert(2, "b");

// ❌ 编译错误:不能同时可变借用和不可变借用
for (k, v) in &map {
    map.remove(&k);
}

// ✅ 方案一:先收集 keys
for k in map.keys().cloned().collect::<Vec<_>>() {
    map.remove(&k);
}

// ✅ 方案二:用 drain(最优雅)
map.drain();

技巧:entry API 是 HashMap 的瑞士军刀

代码语言:javascript
复制
rustlet mut map: HashMap<&str, i32> = HashMap::new();

// 场景1:计数
for word in &["apple", "banana", "apple", "cherry", "banana", "apple"] {
    *map.entry(word).or_insert(0) += 1;
}
// map = {"apple": 3, "banana": 2, "cherry": 1}

// 场景2:只在不存在时插入
map.entry("date").or_insert(1);

// 场景3:修改已存在的值
map.entry("apple").and_modify(|c| *c += 10);

entry API 相比 get + insert 的核心优势:只查一次哈希。 在高频操作中这个差异很明显。


四、HashSet vs BTreeSet:不只是"有没有序"

对比项

HashSet

BTreeSet

查找

O(1) 平均

O(log n)

有序遍历

❌ 随机

✅ 升序

最小/最大值

O(n) 遍历

O(log n)

适用

纯去重、存在性判断

需要范围查询、有序输出

代码语言:javascript
复制
rustuse std::collections::{HashSet, BTreeSet};

let mut hs = HashSet::new();
let mut bs = BTreeSet::new();

for i in [5, 1, 9, 3, 7] {
    hs.insert(i);
    bs.insert(i);
}

// HashSet 遍历顺序不确定
println!("{:?}", hs.iter().collect::<Vec<_>>()); // 可能是 [1, 3, 5, 7, 9] 也可能乱序

// BTreeSet 保证升序
println!("{:?}", bs.iter().collect::<Vec<_>>()); // 一定是 [1, 3, 5, 7, 9]

// 需要范围查询?只能用 BTreeSet
let range: Vec<_> = bs.range(3..=7).copied().collect();
// [3, 5, 7]

五、进阶技巧:这些组合拳让代码更 Rust

1. 用 iter() 链式操作替代手动循环

代码语言:javascript
复制
rustlet v = vec![1, 2, 3, 4, 5, 6];

// 手动循环
let mut sum = 0;
for &x in &v {
    if x % 2 == 0 {
        sum += x * x;
    }
}

// ✅ 链式迭代器,零中间分配
let sum: i32 = v.iter()
    .filter(|&&x| x % 2 == 0)
    .map(|&x| x * x)
    .sum();

2. collect() 的类型推导

代码语言:javascript
复制
rustlet v = vec!["1", "2", "3"];

// ❌ 编译报错:类型不明确
let nums: Vec<i32> = v.iter().map(|s| s.parse()).collect();

// ✅ turbofish 显式指定
let nums: Vec<i32> = v.iter()
    .map(|s| s.parse().unwrap())
    .collect();

// ✅ 或用 collect 直接推断(更常用)
let nums: Vec<i32> = v.iter()
    .filter_map(|s| s.parse().ok())
    .collect();

3. retain 代替 filter + collect

代码语言:javascript
复制
rustlet mut v = vec![1, 2, 3, 4, 5, 6];

// filter + collect:分配新 Vec
let evens: Vec<_> = v.iter().filter(|&&x| x % 2 == 0).copied().collect();

// ✅ retain:原地修改,零分配
v.retain(|&x| x % 2 == 0);

retain 在大数据量下优势明显,因为它不分配新内存。


六、性能选型速查表

你的需求

推荐类型

原因

随机访问 + 尾部增删

Vec

缓存友好,O(1) 尾部操作

头部 + 尾部增删

VecDeque

两端 O(1),中间 O(n)

中间频繁插入/删除

LinkedList

O(1) 插入删除,但缓存不友好

Key-Value 查找

HashMap

平均 O(1),无序

Key-Value + 有序遍历

BTreeMap

O(log n),有序

去重 + 快速查找

HashSet

平均 O(1)

去重 + 有序 + 范围查询

BTreeSet

O(log n)

优先级队列

BinaryHeap

O(log n) 插入,O(1) 取最大


七、写在最后

Rust 集合类型的设计哲学是 "零成本抽象"——每种类型都有明确的性能特征和适用边界。掌握它们的关键不是记住 API,而是理解 每种结构在内存中是怎么组织的,这样你才能在写代码时本能地做出正确选择。

下次再遇到"该用什么集合"的问题,先问自己三个问题:要不要有序?要不要按 Key 查?要不要频繁中间插入? 答案就出来了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Rust 的集合类型是日常开发中最高频的数据结构,但很多人只会 push 和 get。本文从零梳理 Rust 核心集合类型的选用逻辑、实战技巧与常见坑点,帮你真正"掌握"而非"会用"。
    • 一、先搞清:Rust 集合类型到底有哪些?
    • 二、Vec:远不止 push_back
      • 技巧 1:用 with_capacity 避免重复分配
      • 技巧 2:extend 比循环 push 快得多
      • 技巧 3:drain 是被严重低估的方法
    • 三、HashMap:90% 的人踩过这些坑
      • 坑 1:直接用 [] 取值会 panic
      • 坑 2:遍历中修改 HashMap
      • 技巧:entry API 是 HashMap 的瑞士军刀
    • 四、HashSet vs BTreeSet:不只是"有没有序"
    • 五、进阶技巧:这些组合拳让代码更 Rust
      • 1. 用 iter() 链式操作替代手动循环
      • 2. collect() 的类型推导
      • 3. retain 代替 filter + collect
    • 六、性能选型速查表
    • 七、写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档