跳转到内容

Rust HashMap

HashMap 以键值对的形式存储数据:你按键(key)来查找,而不是像数组或 vector 那样按位置查找。如果你用过 Python 的字典,或者在 JavaScript 里把对象当成映射来用,那就是同一个思路。

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("小红", 30);
ages.insert("小刚", 25);
println!("{:?}", ages);
}

开头的 use std::collections::HashMap; 把这个类型引入作用域。和 Vec、String 不同,HashMap 默认是用不了的,所以这一行必不可少。

▶ 动手试一试
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("小红", 30);
match ages.get("小红") {
Some(age) => println!("小红今年 {age} 岁"),
None => println!("没找到"),
}
}
▶ 动手试一试

.get() 返回的是一个 Option,而不是值本身,因为这个键可能不存在。Option 会在它自己的那一课里详细讲。现在只要知道,用 match 配合 Some/None 就是处理它的标准写法。

用同一个键再插入一次,就会覆盖旧的值:

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("小红", 30);
ages.insert("小红", 31); // 覆盖掉 30
println!("{:?}", ages.get("小红"));
}
▶ 动手试一试

如果只想在键还不存在时才插入值,就用 .entry():

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("小红", 30);
ages.entry("小红").or_insert(99); // 小红已经存在,所以什么都不做
ages.entry("小刚").or_insert(25); // 小刚还不存在,所以插入它
println!("{:?}", ages);
}
▶ 动手试一试
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("小红", 30);
ages.insert("小刚", 25);
for (name, age) in &ages {
println!("{name}今年 {age} 岁");
}
}
▶ 动手试一试

只要你想按名字或 ID 查找东西,而不是按位置查找,就用 HashMap:统计单词出现的次数、把用户名映射到用户数据、按键缓存计算结果。如果顺序很重要,或者只是处理一个普通的列表,Vec 更合适。

在一个 HashMap 里存三个名字和对应的电话号码。然后查找 "小王"(在电话簿里)和 "小赵"(不在电话簿里),为每个人打印一行友好的提示。提示:遍历 ["小王", "小赵"],再对 .get(name) 的结果用 match。

查看答案
use std::collections::HashMap;
fn main() {
let mut phone_book = HashMap::new();
phone_book.insert("小李", "555-1234");
phone_book.insert("小王", "555-9876");
phone_book.insert("小陈", "555-4321");
for name in ["小王", "小赵"] {
match phone_book.get(name) {
Some(number) => println!("{name}:{number}"),
None => println!("{name}:不在电话簿里"),
}
}
}

输出:

小王:555-9876
小赵:不在电话簿里
▶ 动手试一试

遍历我们想查的名字,而不是遍历整个 map,顺便还能得到固定的顺序。每当你需要 HashMap 给出可预测的输出时,这都是个好用的小技巧。

你有这些投票:["红色", "蓝色", "红色", "绿色", "蓝色", "红色"]。统计每种颜色得了多少票,然后打印 "红色"、"绿色"、"蓝色" 和 "粉色"(一票都没有)的票数。提示:counts.entry(color).or_insert(0) 会给你这个颜色当前的票数,从 0 开始。用 *count += 1 给它加一。

查看答案
use std::collections::HashMap;
fn main() {
let votes = ["红色", "蓝色", "红色", "绿色", "蓝色", "红色"];
let mut counts = HashMap::new();
for color in votes {
let count = counts.entry(color).or_insert(0);
*count += 1;
}
for color in ["红色", "绿色", "蓝色", "粉色"] {
match counts.get(color) {
Some(n) => println!("{color}:{n}"),
None => println!("{color}:0"),
}
}
}

输出:

红色:3
绿色:1
蓝色:2
粉色:0
▶ 动手试一试

or_insert(0) 返回的是 map 里那个值的可变引用,而不是一份副本。*count += 1 里的 * 表示“修改它指向的那个数字”,所以 map 本身就被更新了。“先 entry,没有就插入 0,再加 1”,这是 Rust 里计数的标准写法。

  • HashMap<K, V> 存储键值对,要先用 use std::collections::HashMap; 把它引入作用域。
  • .insert(key, value) 添加或覆盖值,.get(key) 查找值并返回一个 Option。
  • .entry(key).or_insert(value) 只在键还不存在时才插入。
  • 遍历的顺序没有保证,不要依赖它。

小测验

1. 和 Vec、String 不同,要把 HashMap 引入作用域需要做什么?

2. .entry(key).or_insert(value) 是做什么的?

3. 遍历 HashMap 时,能依赖元素打印出来的顺序吗?

得分: 0 / 3