HashMap
Rust 的 HashMap
是一个基于哈希表的键值对集合,它提供了高效的数据查找、插入和删除操作(平均时间复杂度 O(1))。它是 Rust 标准库 std::collections
的一部分,使用前需要引入。
核心概念
- **键值对 (Key-Value Pair)**: 每个元素由一个唯一的
key
和对应的 value
组成。
- **所有权 (Ownership)**:
- 插入时:
HashMap
会取得键值对的所有权(对于实现了 Copy
trait 的类型如 i32
会复制)。
- 查询时:通常返回对值的引用(
Option<&V>
),避免所有权转移。
- 哈希与相等性:
- 键的类型
K
必须实现 Eq
(判断相等)和 Hash
(计算哈希值)两个 trait。
- 基础类型(如
String
、i32
等)已自动实现,自定义类型可通过 #[derive(PartialEq, Eq, Hash)]
实现。
基础用法与实例
1. 创建 HashMap
1 2 3 4 5 6 7 8 9
| use std::collections::HashMap;
let mut scores: HashMap<String, i32> = HashMap::new();
let teams = vec!["Blue", "Red"]; let initial_scores = vec![10, 50]; let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
|
2. 插入/更新数据
1 2 3 4 5 6 7 8 9 10 11 12
| let mut map = HashMap::new();
map.insert(String::from("Blue"), 10); map.insert(String::from("Red"), 50);
map.insert(String::from("Blue"), 25);
map.entry(String::from("Yellow")).or_insert(50); map.entry(String::from("Blue")).or_insert(50);
|
3. 查询数据
1 2 3 4 5 6 7 8 9 10 11 12
| let team_name = String::from("Blue");
match map.get(&team_name) { Some(score) => println!("{}: {}", team_name, score), None => println!("Team not found"), }
for (key, value) in &map { println!("{}: {}", key, value); }
|
4. 更新值(基于旧值)
1 2 3 4 5 6 7 8 9 10 11 12 13
| let text = "hello world wonderful world";
let mut word_count = HashMap::new();
for word in text.split_whitespace() { let count = word_count.entry(word).or_insert(0); *count += 1; }
println!("{:?}", word_count);
|
5. 删除数据
1 2 3 4 5
| map.remove(&String::from("Red"));
map.clear();
|
常见场景与技巧
1. 处理可能不存在的键
1
| let score = map.get("Green").copied().unwrap_or(0);
|
这个例子中,get() 返回一个 Option<&T>
,copied() 将 Option<&T>
转变成 Option<T>
,unwrap()直接将 Option<T>
中的 T 取出来。
2. 合并两个 HashMap
1 2 3 4 5
| let mut map1 = HashMap::from([("a", 1), ("b", 2)]); let map2 = HashMap::from([("b", 3), ("c", 4)]);
map1.extend(map2);
|
3. 自定义键类型
1 2 3 4 5 6 7 8 9 10 11 12 13
| #[derive(Debug, Hash, Eq, PartialEq)] struct Student { id: u32, name: String, }
let mut students = HashMap::new(); students.insert( Student { id: 1, name: "Alice".to_string() }, "Grade A" );
println!("{:?}", students.get(&Student { id: 1, name: "Alice".to_string() }));
|
注意事项
- 性能影响:哈希冲突可能降低性能(Rust 默认使用抗碰撞的 SipHash 算法)。
- 内存开销:HashMap 会预分配内存以提升效率。
- 线程安全:默认非线程安全,多线程需用
Mutex<HashMap>
或 RwLock<HashMap>
。
完整示例:单词计数器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| use std::collections::HashMap;
fn main() { let text = "apple banana orange apple banana"; let mut counter = HashMap::new();
for word in text.split_whitespace() { *counter.entry(word).or_insert(0) += 1; }
println!("Word counts:"); for (word, count) in &counter { println!("{}: {}", word, count); } }
|
问题1:这个 unwrap() 取出来的 T 是拷贝还是引用?
unwrap()
返回的是 值的所有权(owned value),而不是引用。
unwrap()
的本质
1 2 3 4 5 6 7 8 9
| impl<T> Option<T> { pub fn unwrap(self) -> T { match self { Some(val) => val, None => panic!("called `Option::unwrap()` on a `None` value"), } } }
|
所有权转移的三种情况
1. 基础类型(实现 Copy
trait)
1 2 3 4 5
| let num: Option<i32> = Some(42); let unwrapped_num = num.unwrap();
println!("{}", unwrapped_num);
|
2. 堆分配类型(未实现 Copy
)
1 2 3 4 5 6
| let name: Option<String> = Some("Alice".to_string()); let unwrapped_name = name.unwrap();
println!("{}", unwrapped_name);
|
3. 引用类型(特殊处理)
1 2 3 4 5 6 7
| let data = "Hello".to_string(); let data_ref: Option<&String> = Some(&data);
let unwrapped_ref = data_ref.unwrap();
println!("{}", unwrapped_ref);
|
关键行为总结
情况 |
unwrap() 返回类型 |
所有权变化 |
后续使用限制 |
Option<T> (T: Copy) |
T (值拷贝) |
原变量仍可用 |
无限制 |
Option<T> (T: !Copy) |
T (所有权转移) |
原变量失效 |
不能再用原 Option |
Option<&T> |
&T (借用) |
仅借用 |
需遵守借用规则 |
Result<T, E> |
同上规则 |
同上规则 |
同上规则 |
对比其他方法的返回值
方法 |
返回类型 |
所有权状态 |
是否可能 panic |
unwrap() |
T |
所有权转移 |
是 |
unwrap_ref() |
&T |
借用 |
是 |
as_ref() |
Option<&T> |
借用 |
否 |
copied() |
Option<T> |
拷贝(需 T: Copy) |
否 |
cloned() |
Option<T> |
克隆(需 T: Clone) |
否 |
实际内存演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let value = String::from("Heap Data");
let opt = Some(value); let unwrapped = opt.unwrap();
let opt2 = Some(String::from("Another")); let ref_view = opt2.as_ref().unwrap();
|
最佳实践建议
需要所有权时
直接使用 unwrap()
获取值
1 2
| let config: Option<String> = ; let config_value = config.unwrap();
|
只需读取值时
使用 as_ref().unwrap()
避免转移
1 2 3
| let name: Option<String> = Some("Alice".into()); print_name(name.as_ref().unwrap());
|
需要修改值但保留 Option 时
使用 as_mut().unwrap()
1 2
| let mut counter: Option<u32> = Some(0); *counter.as_mut().unwrap() += 1;
|
处理引用容器时
使用 copied()
/cloned()
明确所有权
1 2 3
| let numbers = [1, 2, 3]; let opt_ref: Option<&i32> = numbers.get(1); let value: i32 = opt_ref.copied().unwrap();
|
总结图
1 2 3 4 5 6 7 8 9 10 11 12 13
| 调用 unwrap() 时: 如果 T 是值类型 → 返回 T (拷贝) 如果 T 是堆类型 → 返回 T (所有权转移) 如果 T 是引用 → 返回 &T (借用)
+---------------------+ | Option<T> | | +---------------+ | | | Some(value) |--[unwrap()]--> value (所有权转移) | +---------------+ | | | None |--[unwrap()]--> PANIC! | +---------------+ | +---------------------+
|