Rust学习总结之HashMap

        用过Python的相信对字典这个概念不陌生,其key-value的存储模式大大方便的开发者进行数据的存储和查找,所以字典在Python的开发中用的非常多,那新兴语言Rust会跟进这一优秀的数据存储结构的设计吗。在此本文的女主角登场了,Rust中的HashMap结构就是类似于Python的字典角色。

一:HashMap概念

    HashMap<K, V> 类型储存了一个键类型 K 对应一个值类型 V 的映射。它通过一个 哈希函数hashing function)来实现映射,决定如何将键和值放入内存中。它通过一个 哈希函数hashing function)来实现映射,决定如何将键和值放入内存中。需要注意的是HashMap<K, V> 存储的数据是放在堆内存里的。

        HashMap可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。类似于数据库的设计,只要我们在存储数据的时候设计好value的key,那么我们就可以根据key立马得到我们想要的value。

一:HashMap创建

use std::collections::HashMap;
fn main() {
    let mut person = HashMap::new();
}

        我们用HashMap的new函数来创建一个新的空的HashMap,需要注意的是我们开头用了use std::collections::HashMap;来引进HashMap。那是因为并没有被 prelude 自动引用。标准库中对 HashMap 的支持也相对较少。在这我个人感觉HashMap这种好用的结构没有得到应有的尊重。

        另外在学习Rust的小伙伴也知道,Rust语言为了保证安全性,通常会加很多严格的校验,HashMap也一样。像 vector 一样,哈希 map 将它们的数据储存在堆上,类似于 vector,哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。相比于Python字典的包容性,感觉Rust在设计字典的时候就和小姑娘谈恋爱约会一样,怕你不来,又怕你乱来。设计者怕你不用HashMap,又怕你乱用HashMap。

二:HashMap插入

        HashMap插入常用的有两种方法,一是通过insert方法,二是使用一个元组的 vector 的 collect 方法。

        先来看看insert方法,我们用名字作为索引,value对应名字的年龄,构成name-age的键值对。

use std::collections::HashMap;
fn main() {
    let mut person = HashMap::new();
    person.insert(String::from("ftz"), 30);
    person.insert(String::from("zky"), 18);

    println!("{:?}",person);
}

运行结果:

         第二种构建哈希 map 的方法是使用一个元组的 vector 的 collect 方法,其中每个元组包含一个键值对。collect 方法可以将数据收集进一系列的集合类型,包括 HashMap。例如,如果所有人的名字和年龄分别在两个 vector 中,可以使用 zip 方法来创建一个元组的 vector,可以把zip想象成一个拉链,连接两个元组,其中 “ftz” 与 30 是一对,依此类推。接着就可以使用 collect 方法将这个元组 vector 转换成一个 HashMap。

use std::collections::HashMap;
fn main() {
    let name = vec!["ftz","zky"];
    let age  = vec![30, 18];

    let person: HashMap<_,_> = name.iter().zip(age.iter()).collect();
    println!("{:?}",person);
}

运行结果:

         需要注意的是 HashMap<_, _> 类型标注是必要的,因为 collect 有可能当成多种不同的数据结构,而除非显式指定否则 Rust 无从得知你需要的类型。但是对于键和值的类型参数来说,可以使用下划线占位,而 Rust 能够根据 vector 中数据的类型推断出 HashMap 所包含的类型。

三:HashMap更新

        有插入就会有更新,上面的例子,人名对应的年龄,人名是不变的,但是年龄会一年增长一岁,就需要对value进行更新。

        对HashMap的更新存在两种情况,如何处理一个键已经有值了的情况。可以选择完全无视旧值并用新值代替旧值。可以选择保留旧值而忽略新值,并只在键 没有 对应值时增加新值。或者可以结合新旧两值。

1,直接更新覆盖

use std::collections::HashMap;
fn main() {
    let mut person = HashMap::new();
    person.insert(String::from("ftz"), 30);
    person.insert(String::from("zky"), 18);

    person.insert(String::from("zky"), 19);
    println!("{:?}",person);
}

运行结果:

 2,选择更新插入

        这里需要解释一下什么是选择更新覆盖,就是覆盖是有前提的,那就是要插入的key-value键值对在现有的HashMap中key不存在的情况下,可以直接插入,但是在key存在的情况下就不进行插入。

use std::collections::HashMap;
fn main() {
    let mut person = HashMap::new();
    person.insert(String::from("ftz"), 30);
    person.insert(String::from("zky"), 18);

    person.entry(String::from("ftz")).or_insert(31);
    person.entry(String::from("ftzSon")).or_insert(1);
    println!("{:?}",person);
}

运行结果:

         哈希 map 有一个特有的 API,叫做 entry,它获取我们想要检查的键作为参数。entry 函数的返回值是一个枚举,Entry,它代表了可能存在也可能不存在的值。Entry 的 or_insert 方法在键对应的值存在时就返回这个值的可变引用,如果不存在则将参数作为新值插入并返回新值的可变引用。

3,基于旧值更新

        另一个常见的哈希 map 的应用场景是找到一个键对应的值并根据旧的值更新它。例如,下面代码计数一些文本中每一个单词分别出现了多少次。我们使用哈希 map 以单词作为键并递增其值来记录我们遇到过几次这个单词。如果是第一次看到某个单词,就插入值 0

use std::collections::HashMap;
fn main() {
    let text = "hello world wonderful world";

    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }
    println!("{:?}", map);
}

运行结果:

    or_insert 方法事实上会返回这个键的值的一个可变引用(&mut V)。这里我们将这个可变引用储存在 count 变量中,所以为了赋值必须首先使用星号(*)解引用 count。这个可变引用在 for 循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则。 

拿上面的一个例子,名字对应的年龄过了一年都会长一岁

use std::collections::HashMap;
fn main() {
    let mut person = HashMap::new();
    person.insert(String::from("ftz"), 30);
    person.insert(String::from("zky"), 18);
 
    let mut person2 = HashMap::new();
    for (key, value) in person {
        let age = person2.entry(key.to_string()).or_insert(value);
        *age += 1;
    }
    println!("{:?}",person2);
}

运行结果:

 

四:HashMap访问

可以通过 get 方法并提供对应的键来从哈希 map 中获取值

fn main() {
    let mut person = HashMap::new();
    person.insert(String::from("ftz"), 30);
    person.insert(String::from("zky"), 18);

    let ftz_age = person.get("ftz");
    println!("ftz age is {:?}",ftz_age);
}

运行结果:

可以看到打印出来的是Some(30),说明 get 返回 Option<V>,所以结果被装进 Some;如果某个键在哈希 map 中没有对应的值,get 会返回 None。这时候正规的写法就是用match

use std::collections::HashMap;
fn main() {
    let mut person = HashMap::new();
    person.insert(String::from("ftz"), 30);
    person.insert(String::from("zky"), 18);

    let ftz_age = person.get("ftz");
    match ftz_age{
        Some(ftz_age) => println!{"ftz age is {}",ftz_age},
        None          => println!{"no ftz"},
    }
}

运行结果:

 五:HashMap遍历

我们用for循环来遍历HashMap

use std::collections::HashMap;
fn main() {
    let mut person = HashMap::new();
    person.insert(String::from("ftz"), 30);
    person.insert(String::from("zky"), 18);

    for (key,value) in &person {
        println!("the {} age is {}",key,value);
    }
}

运行结果:

 六:HashMap所有权

        对于像 i32 这样的实现了 Copy trait 的类型,其值可以拷贝进哈希 map。对于像 String 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者。

use std::collections::HashMap;
fn main() {
    let mut person = HashMap::new();
    let ftz_name = String::from("ftz");
    let ftz_age = 30;
    person.insert(ftz_name, ftz_age);

    println!("the {} age is {}",ftz_name,ftz_age);
}

 可以看到上面代码给出的提示ftz_name的所有权被移动了,即在打印的时候已经不能访问ftz_name这个变量了,但是由于ftz_age的类型是i32其值是拷贝进去的,所有仍然有其所有权。如果将值的引用插入到HashMap,值本身不会移动也就能正常访问了

use std::collections::HashMap;
fn main() {
    let mut person = HashMap::new();
    let ftz_name = String::from("ftz");
    let ftz_age = 30;
    person.insert(&ftz_name, ftz_age);

    println!("the {} age is {}",ftz_name,ftz_age);
}

运行结果:

 七:总结

        Rust中的HashMap实现了Python中的字典结构,但是条条框框太多了,想用好确实不容易,至少我学完还是没有完全能掌握,后面继续学习才能深入理解Rust。

猜你喜欢

转载自blog.csdn.net/qq_27071221/article/details/129893299