17_rust的HashMap
HashMap<K, V>
键值对的形式存储数据,一个键(key)对应一个值(value)。适用于通过K(任何类型)来搜索数据,而不是通过下标索引的场景。
Hash函数:决定如何在内存中存放K和V。
创建HashMap
new函数创建
创建空HashMap:new()函数。
添加数据:insert()方法。
use std::collections::HashMap;
fn main() {
let mut m: HashMap<String, i32> = HashMap::new();
let mut m2 = HashMap::new();
m2.insert(String::from("test"), 10); // 通过这行推断出m2的键值类型
}
- HashMap用的较少,不在Prelude中。
- 标准库对HashMap支持也较少,没有内置的宏来创建HashMap。
- 其数据存放在heap上。
- 数据类型同构,一个HashMap的所有k和v都必须是同一类型。
collect方法创建
另一种创建HashMap采用collect方法,但场景比较特定。在元素类型为Tuple的Vector上使用collect方法,可创建一个HashMap:要求Tuple有两个值,一个为K、一个为V。
collect方法可把数据整合成很多种集合类型,包括HashMap。返回值需显示指明类型。
use std::collections::HashMap;
fn main() {
let t = vec![String::from("t1"), String::from("t2")];
let v = vec![10, 20];
// 这里必须指明类型,因为collect可以返回多种集合数据结构,如果不指明则无法知道是想要map还是其他,
// 里边的类型使用了下划线(_),编译器可根据vec里的数据类型推断出k和v的类型
let m: HashMap<_, _> =
t.iter().zip(v.iter()).collect(); //通过zip方法进行合并
}
HashMap的所有权
对于实现了Copy trait的类型(如i32),值会被复制到HashMap中。
对于拥有所有权的值(如String),值会被移动,所有权会转移给HashMap。
use std::collections::HashMap;
fn main() {
let k = String::from("key");
let v = 20;
let mut m = HashMap::new();
m.insert(k, v);
println!("{}{}", k, v); // 编译报错borrow of moved value: `k`,value borrowed here after move
}
如果将值的引用插入到HashMap,值本身不会移动。注意:在HashMap有效的期间,被引用的值必须保持有效。
use std::collections::HashMap;
fn main() {
let k = String::from("key");
let v = 20;
let mut m = HashMap::new();
m.insert(&k, v);
println!("{}{}", k, v); // 编译通过
}
访问HashMap的值
get()方法,参数:K,返回值Option<&V>。
use std::collections::HashMap;
fn main() {
let mut m = HashMap::new();
m.insert(String::from("k1"), 10);
m.insert(String::from("k2"), 20);
let s = String::from("k2");
let ret = m.get(&s);
match ret {
Some(v) => println!("v={}", v),
None => println!("is None"),
}
}
// 打印v=20
遍历HashMap
使用for循环。根据打印结果,可见遍历访问到的键值对顺序是不确定的。
use std::collections::HashMap;
fn main() {
let mut m = HashMap::new();
m.insert(String::from("k1"), 10);
m.insert(String::from("k2"), 20);
m.insert(String::from("k3"), 30);
for (k, v) in &m { // 使用引用,m所有权保留,返回后模式匹配
println!("{}:{}", k, v);
}
}
/*运行结果
第一次:
k1:10
k3:30
k2:20
第二次:
k3:30
k1:10
k2:20
第三次:
k1:10
k2:20
k3:30
第四次:
k3:30
k2:20
k1:10
*/
更新HashMap
HashMap的大小可变,每个K同时只能对应一个V(一个Map内无重复Key)。
更新HashMap的数据:
- 如果K已存在,对应一个V,有以下三种情况:
- 替换现有的V
- 保留现有的V,忽略新的V
- 合并现有的V和新的V
- 如果K不存在,则添加一对K-V
替换现有V
如果向HashMap插入一对KV,然后插入同样的K,但不同V,则会替换原来V的情况。
use std::collections::HashMap;
fn main() {
let mut m = HashMap::new();
m.insert(String::from("k1"), 10);
m.insert(String::from("k1"), 20);
m.insert(String::from("k3"), 30);
println!("{:?}", m);
}
// 结果 {"k1": 20, "k3": 30}
保留V,忽略新V
只在K不对应任何值的情况下,才插入V。
需要使用entry方法:检查指定的K是否对应一个V。参数为K,返回值enum Entry,代表值是否存在。
Entry的or_insert()方法:
- 如果K存在,返回到对应的V的一个可变引用
- 如果K不存在,将方法参数作为K的新值插入HashMap,返回到这个值的可变引用
use std::collections::HashMap;
fn main() {
let mut m = HashMap::new();
m.insert(String::from("k1"), 10);
let e = m.entry(String::from("k2"));
println!("{:?}", e);
e.or_insert(20); // 因为k2不存在,所以执行插入20的值
m.entry(String::from("k1")).or_insert(30);//也是先判断k1是否存在,如果存在则不会执行插入k1:30的值
println!("{:?}", m);
}
/* 运行结果:
Entry(VacantEntry("k2")) // Vacant空缺,VacantEntry变体表示该值不存在,也就是k2这个key不存在
{"k1": 10, "k2": 20}
*/
基于现有V更新V
use std::collections::HashMap;
//统计单词出现的次数
fn main() {
let text = "test world count test num1 num1";
let mut m = HashMap::new();
for w in text.split_whitespace() { // 按照空格切割,并遍历
let cnt = m.entry(w).or_insert(0); // 返回值的可变引用,如果已存在则不插入,只返回值可变引用
*cnt += 1; // 解引用+1,计数一次
}
println!("{:?}", m);
}
//运行结果:{"num1": 2, "world": 1, "test": 2, "count": 1}
Hash函数
默认情况下,HashMap使用加密功能强大的Hash函数,可抵抗拒绝服务(Dos)攻击,具有较好的安全性,但不是可用的最快Hash算法。
可指定不同hasher来切换另外一个函数,hasher是实现BuildHasher trait的类型。