【Rust自学】8.6. HashMap Pt.2:更新HashMap
8.6.0. 本章内容
第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构,这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。
第八章中的集合是存储在堆内存上而非栈内存上的,这也意味着这些集合的数据大小无需在编译时就确定,在运行时它们可以动态地变大或变小。
本章主要会讲三种集合:Vector、String和HashMap(本文)
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
8.6.1. 更新HashMap
HashMap的大小可变指的是其中的键值对数量可变,但是在每个时刻一个键只能对应一个值,当想要更新HashMap中的数据的时候,可能有这么几种情况:
-
想要更新的键在HashMap中已经存在一个对应的值:
- 用新的值代替现有的值
- 保留现有的值,忽略新的值
- 合并现有的值和新的值,也就是说对现有的值进行修改
-
键不存在:添加一对键和值
1. 替换(覆盖)现有的值
如果向HashMap插入一对键值对,但键已经存在,程序就会把新值赋给这个键,原来的就被覆盖了。如下例:
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("dev1ce"), 0);
scores.insert(String::from("dev1ce"), 60);
println!("{:?}", scores)
}
这里为通一个键赋了两次值,第一次是0,第二次是60,那么第一次的就会被第二次覆盖掉,也就是说,最终"dev1ce"对应的值是60。
输出:
{"dev1ce": 60}
2. 只在键不存在任何值的情况下才插入值
这个情况是最常见的,对于这种情况,首先需要检查原HashMap中是否已经存在这个键,如果不存在再插入新值。
Rust提供了entry
这个方法来实现检查原HashMap中是否已经存在这个键的功能。它的参数为键,它的返回值是一个枚举Entry
,代表值是否存在。看个例子:
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("dev1ce"), 0);
let e = scores.entry(String::from("dev1ce"));
println!("{:?}", e);
}
这是键已经存在的情况,来看一下输出:
Entry(OccupiedEntry { key: "dev1ce", value: 0, .. })
也就是说,如果键已经存在,那么entry
这个方法会返回Entry
枚举下的OccupiedEntry
这个变体并且关联已经存在的键值对。
那来试一下键不存在的情况。代码如下:
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("dev1ce"), 0);
let e = scores.entry(String::from("Zywoo"));
println!("{:?}", e);
}
输出:
Entry(VacantEntry("Zywoo"))
如果键不存在,那么它会返回Entry
枚举下的VacantEntry
变体,并且关联这个新的键。
现在有办法检查原HashMap中是否已经存在这个键,那么如何根据是否存在实现插入或不插入值呢?
Rust提供了or_insert
方法,其参数是想要添加的值,它可以接收Entry
枚举类型,根据这个类型的两个变体来实现是否插入的功能。如果它接收到了OccupiedEntry
(已经存在的情况)这个变体,它就会不插入值;反之,如果接收到了VacantEntry
(不存在的情况)变体,它就不会执行插入操作。最重要的一点是,它是有返回值的,它的返回值是这个键对应值的可变引用(存在就返回HashMap中原有的键所对应的值的可变引用,不存在就先添加键值对然后返回值的可变引用),根据这个特性可以实现一些简单的计数器(后文会讲)。
看下例子:
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("dev1ce"), 0);
scores.entry(String::from("Zywoo")).or_insert(100);
scores.entry(String::from("dev1ce")).or_insert(60);
println!("{:?}", scores);
}
- 第一个
entry
语句查找了"Zywoo",发现没有,就返回VacantEntry
,or_insert
接收到了,就根据VacantEntry
所关联的键和传入的参数100创建了(“Zywoo”, 100)这个键值对。 - 第二个
entry
语句查找了"dev1ce",发现有了,就返回OccupiedEntry
,or_insert
接收到了,就停止插入新值,所以会保持(“dev1ce”, 0)不变。
输出:
{"Zywoo": 100, "dev1ce": 0}
如果这么讲还有些复杂,那么你可以把scores.entry(String::from("Zywoo")).or_insert(100);
看作两行代码:
let e = `scores.entry(String::from("Zywoo"));
e.or_insert(100);
3.基于现有的值来更新
先看例子:
use std::collections::HashMap;
fn main() {
let text = "That's one small step for [a] man, one giant leap for mankind.";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:#?}", map);
}
- 这个例子首先声明了一个字符串字面值,一段话,赋给了
text
。 - 然后创建了
map
这个HashMap - 接着使用了
for
循环,text.split_whitespace()
就是把text
分割为了一组字符串的遍历器,用for
来遍历。 - 遍历的时候,就判断单词在这个
map
里出现没,出现了就不插入新值了,没出现就插入0作为新的键值对。最重要的是理解count
,由于or_insert
方法的返回值是这个键对应值的可变引用,所以没出现一次就把值的可变引用先解引用,在自加1就相当于完成了一次统计。
8.6.2. Hash函数
在默认情况下,HashMap使用加密功能强大的Hash函数,可以抵抗拒绝服务(DoS)攻击。但这种函数它不是可用的最快的Hash算法,它的优势是具有较好的安全性。如果觉得它性能不好,也可以指定不同的hasher来切换到另一个函数。hasher指的是实现BuildHasher trait的类型。