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

posted @ 2023-10-25 15:35  00lab  阅读(58)  评论(0编辑  收藏  举报