likes
comments
collection
share

Rust与HashMap

作者站长头像
站长
· 阅读数 43

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的类型)

转载自:https://juejin.cn/post/7176617858070708282
评论
请登录