讲解

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。