讲解
HashMap<K, V> 存储键值对,来自 std::collections(不在预导入的 prelude 里,要 use 引入)。基本操作四件套:insert(k, v) 插入(键已存在则覆盖旧值)、get(&k) 查询(返回 Option<&V>,不存在是 None——查询必须处理空)、remove(&k) 删除、contains_key(&k) 判存在。所有权规则不变:insert 会拿走键值的所有权(String 键值被移进 map),用 i32 这类 Copy 类型则复制。
最有 Rust 特色的是 entry API:map.entry(key).or_insert(default) 返回对应值的可变引用——「有则取之,无则插入默认值再取之」一步完成。经典的词频统计 *counts.entry(word).or_insert(0) += 1 就是靠它一行写完,其他语言里要先判存在再分支的啰嗦完全消失。
两个使用注意。一是迭代顺序:HashMap 不保证任何顺序,遍历输出每次运行可能不同——需要稳定顺序就把条目 collect 到 Vec 里排序,或者改用 BTreeMap(键有序,性能略低)。二是集合家族的其他成员:HashSet 是「只有键」的 HashMap(去重、交集并集),BTreeSet 是有序版,VecDeque 是双端队列——先掌握 HashMap,其余随用随查。
示例
insert / get / entry 三板斧:
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("蓝队"), 10);
scores.insert(String::from("蓝队"), 25); // 覆盖旧值
scores.insert(String::from("黄队"), 50);
// get 返回 Option,必须处理「不存在」
let blue = scores.get("蓝队").copied().unwrap_or(0);
let green = scores.get("绿队").copied().unwrap_or(0);
assert_eq!(blue, 25);
assert_eq!(green, 0);
// entry:不存在则插入 0,返回可变引用后自增
*scores.entry(String::from("黄队")).or_insert(0) += 5;
assert_eq!(scores.get("黄队"), Some(&55));
println!("蓝队 {blue} 分,黄队 55 分,绿队 {green} 分");
}
实战:词频统计 + 稳定顺序输出(先排序再打印):
use std::collections::HashMap;
fn main() {
let text = "hello world hello rust rust rust";
let mut counts: HashMap<&str, u32> = HashMap::new();
for word in text.split_whitespace() {
*counts.entry(word).or_insert(0) += 1;
}
assert_eq!(counts.get("rust"), Some(&3));
assert_eq!(counts.get("hello"), Some(&2));
// HashMap 迭代顺序不定:排序后输出才稳定
let mut sorted: Vec<(&&str, &u32)> = counts.iter().collect();
sorted.sort_by(|a, b| b.1.cmp(a.1)); // 按次数降序
for (word, count) in sorted {
println!("{word}: {count}");
}
}
常见坑
- 直接 unwrap get 的结果:get 返回 None 时 unwrap 就 panic;先想清楚「键不在该怎么办」,unwrap_or 或 match 处理。
- 依赖迭代顺序写断言:HashMap 的顺序连两次运行间都可能变,测试里对顺序敏感的断言会偶发失败——排序后再比较。
- insert 覆盖不知情:同键 insert 静默覆盖旧值;只想在不存在时插入用 entry().or_insert(),或先 contains_key 判断。
- 用 String 键又总想拿字面量查:get("键") 能编译靠的是 String 的 Borrow 实现,确实可行;但反过来用 &str 当键存进了谁的生命周期就要想清楚(第 21 章)。
小结
HashMap:insert/get/remove 三件套,get 必处理 None,entry().or_insert() 是「查或插」神器;迭代无序,要稳定就排序或上 BTreeMap。下一章把「失败」这件事彻底讲透:错误处理与 panic。