HashMap
类似于其它语言的字典,JavaScript中的Map。Rust中的HashMap是键值对的集合,并且它是同构的,本篇文章将对Rust中的HashMap进行介绍。
HashMap\<K,V>
以键值对的形式存储数据,一个key对应一个value
Hash函数:决定如何在内存中存放K和V
适用场景:通过K(任何类型)来寻找数据,而不是通过索引
创建HashMap
创建空HashMap:使用new()函数
use std::collections::HashMap;//需要导入
fn main() {
let mut hm: HashMap<String, i32> = HashMap::new();
}
并且使用insert()方法,添加数据:
let mut hm = HashMap::new();
hm.insert(String::from("yellow"), 8);
HashMap用得较少,所以不在预导入模块中
标准库对其支持比较少,没有内置的宏来创建HashMap
HashMap的数据存储在heap上,并且是同构的。
也就是说,一个HashMap中:
- 所有的K必须是同一种类型
- 所有的V必须是同一种类型
使用collect方法创建HashMap
使用条件比较特殊:在元素类型为Tuple的Vector上使用collect方法,可以组建一个HashMap。
- 要求Tuple有两个值:一个作为K,一个作为V
- collect方法可以把数据整合成很多集合类型,包括HashMap。所以返回值得显示指明类型。
let teams = vec![String::from("white"), String::from("black")];
let intital_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(intital_scores.iter()).collect();
println!("{:?}", scores); //{"white": 10, "black": 50}
其中:
- zip()接受一个参数,将调用者中的元素与参数中的元素一一对应组成Tuple(元组),若数量不匹配,多的元素会丢掉
- 注意声明类型,上面例子的HashMap中的数据类型我们使用
_
代替,rust编译器会根据后续操作自动识别类型。
HashMap和所有权
对于实现了Copy trait的类型如i32,值会被复制到HashMap
对于拥有所有权的值如String,值会被移动,所有权会被转移给HashMap
let file_type = String::from("word");
let file_name = String::from("about_me");
let mut map = HashMap::new();
map.insert(file_type, file_name);
// println!("{},{}", file_name, file_type); //borrow of moved value: `file_type`(被移动了)
println!("{:?}", map); //{"word": "about_me"}
如果将值的引用插入到HashMap,值的本身不会被移动
let file_type = String::from("word");
let file_name = String::from("about_me");
let mut map = HashMap::new();
map.insert(&file_type, &file_name);
println!("{},{}", file_type, file_name); //word,about_me
println!("{:?}", map); //{"word": "about_me"}
但是,在HashMap有效期内,被引用的值必须保持有效
访问HashMap中的值
使用get方法:
- 参数:K
- 返回值:Option<&V>
let mut map = HashMap::new();
map.insert(String::from("white"), 10);
map.insert(String::from("black"), 20);
let team_name = String::from("white");
let team_score = map.get(&team_name);
match team_score {
Some(s) => println!("{}", s),
None => println!("team is not exit"),
}
//10
遍历HashMap
使用for循环
let mut map = HashMap::new();
map.insert(String::from("white"), 10);
map.insert(String::from("black"), 20);
for (k, v) in &map {
println!("{}:{}", k, v);
}
// white:10
// black:20
更新HashMap\<K,V>
HashMap的大小可变,每个Key只能对应一个Value
更新HashMap中的数据时,我们可能遇到以下的情况:
- K已经存在,并且对应了一个V:
-
- 替换现有的V
- 保留现有的V,忽略新的V
- 合并现有的V和新的V
- K不存在
-
- 添加一对K,V
替换现有的V
如果向HashMap插入一对KV,然后再插入同样的K,但是不同的V,那么原来的V会被替换掉:
let mut map = HashMap::new();
map.insert(String::from("white"), 10);
map.insert(String::from("white"), 20);
println!("{:?}", map); //{"white": 20}
只在K不对应任何值时,才插入V
针对这种情况,我们需要使用entry方法:检查指定的K是否对应一个V:
- 参数为K
- 返回enum Entry:代表值是否存在
let mut map = HashMap::new();
map.insert(String::from("white"), 10);
let e = map.entry(String::from("black"));
println!("{:?}", e); //Entry(VacantEntry("black"))
e.or_insert(20);
map.entry(String::from("white")).or_insert(25);
println!("{:?}", map); //{"white": 10, "black": 20}
我们可以看到第5行打印Entry(VacantEntry("black"))
,这表示Entry枚举中black不存在。所以我们可以使用`or_insert()
成功插入V
对于Entry的or_insert()方法:
- 返回:
-
- 如果K存在,返回对于的V的一个可变引用
- 如果K不存在,将方法参数作为K的新值插入,返回这个这的可变引用
基于现有V来更新V
let text = "nice demo yeah yeah yeah";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:#?}", map);
// {
// "nice": 1,
// "demo": 1,
// "yeah": 3,
// }
其中,&str.split_whitespace()方法将字符串切片以空格分割,并且返回一个迭代器。
count是一个可变引用,如果HashMap中有对应的值map.entry(word).or_insert(0)
,返回值的可变引用,如果没有,返回0的可变引用。
我们使用*
来解引用,从而改变可变引用的值。
我们最后看到,nice出现一次,demo出现1次,yeah出现3次。
Hash函数
默认情况下,HashMap使用加密功能强大的Hash函数,可以抵抗拒绝服务(Dos)攻击。关于Hash函数,我们需要知道:
- 不是可用的最快的Hash算法
- 但具有更好的安全性
我们可以指定不同的hasher来切换到另一个函数(hasher是实现BuildHasher trait的类型)