基础篇-HashMap类型

  • 木头
  • 更新于 2023-02-27 16:07
  • 阅读 1748

HashMap创建,插入,读取,修改

和动态数组Vec一样,哈希表(HashMap)也是Rust内置的集合类型之一,同属std::collections模块下。HashMap<K, V>类型储存了一个键类型 K对应一个值类型 V 的映射。它通过一个 哈希函数(hashing function)来实现映射,决定如何将键和值放入内存中。

哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector那样通过索引。

新建一个哈希 map

可以使用 new 创建一个空的 HashMap,并使用 insert增加元素,我们记录两支队伍的分数,分别是蓝队和黄队。蓝队开始有 10 分而黄队开始有 50 分:

use std::collections::HashMap;
fn main() {
    // 声明
    let mut m = HashMap::new();

    // 插入
    m.insert(String::from("Blue"), 10);
    m.insert(String::from("Yellow"), 50);
}

注意必须首先 use 标准库中集合部分的 HashMap。在这三个常用集合中,HashMap 是最不常用的,所以并没有被 prelude自动引用。标准库中对 HashMap 的支持也相对较少。

vector 一样,哈希 map 将它们的数据储存在堆上,这个 HashMap的键类型是 String 而值类型是 i32。类似于 vector,哈希 map是同质的:所有的键必须是相同类型,值也必须都是相同类型。

访问哈希 map中的值

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

use std::collections::HashMap;
fn main() {
    // 声明
    let mut m = HashMap::new();

    // 插入
    m.insert(String::from("Blue"), 10);
    m.insert(String::from("Yellow"), 50);

    let m1 = m.get(&String::from("Blue")).copied().unwrap_or(0);
}

这里,m1是与蓝队分数相关的值,应为 10get 方法返回 Option<&V>,如果某个键在哈希 map中没有对应的值,get 会返回 None。程序中通过调用 copied 方法来获取一个 Option<i32> 而不是 Option<&i32>,接着调用 unwrap_orm1 中没有该键所对应的项时将其设置为零。 利用get的返回判断元素是否存在:

use std::collections::HashMap;
fn main() {
    // 声明
    let mut m = HashMap::new();

    // 插入
    m.insert(String::from("Blue"), 10);
    m.insert(String::from("Yellow"), 50);

    if let Some(m1) = m.get(&String::from("Blue")) {
        println!("{}", m1);
    } else {
        println!("无结果");
    }
}

可以使用与vector类似的方式来遍历哈希 map 中的每一个键值对,也就是 for 循环:

use std::collections::HashMap;
fn main() {
    // 声明
    let mut m = HashMap::new();

    // 插入
    m.insert(String::from("Blue"), 10);
    m.insert(String::from("Yellow"), 50);

    for (k, v) in &m {
        println!("{k}: {v}");
    }
}

这会以任意顺序打印出每一个键值对:

Yellow: 50
Blue: 10

哈希map 和所有权

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

use std::collections::HashMap;
fn main() {
    // 声明
    let mut m = HashMap::new();

    let field_k = String::from("color");
    let field_v = String::from("Blue");

    m.insert(field_k, field_v);
    // 这里 field_k 和 field_v 不再有效,
    // 尝试使用它们看看会出现什么编译错误!
}

insert 调用将 field_k 和 field_v 移动到哈希 map中后,将不能使用这两个绑定。

更新哈希 map

当我们想要改变哈希 map 中的数据时,必须决定如何处理一个键已经有值了的情况。可以选择完全无视旧值并用新值代替旧值。可以选择保留旧值而忽略新值,并只在键 没有 对应值时增加新值。或者可以结合新旧两值。

覆盖一个值

如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换:

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

    m.insert(String::from("Blue"), 10);
    m.insert(String::from("Blue"), 25);

    println!("m {:#?}", m);
}

这会打印出 {"Blue": 25}。原始的值 10 则被覆盖了。

只在键没有对应值时插入键值对

我们经常会检查某个特定的键是否已经存在于哈希 map中并进行如下操作:如果哈希 map 中键已经存在则不做任何操作。如果不存在则连同值一块插入。

为此哈希map 有一个特有的API,叫做 entry,它获取我们想要检查的键作为参数。entry函数的返回值是一个枚举,Entry,它代表了可能存在也可能不存在的值:

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

    m.insert(String::from("Blue"), 10);

    m.entry(String::from("Yellow")).or_insert(50);
    m.entry(String::from("Blue")).or_insert(50);

    println!("{:#?}", m);
}

运行:

{
    "Blue": 10,
    "Yellow": 50,
}

Entryor_insert 方法在键对应的值存在时就返回这个值的可变引用,如果不存在则将参数作为新值插入并返回新值的可变引用。

根据旧值更新一个值

另一个常见的哈希map 的应用场景是找到一个键对应的值并根据旧的值更新它,经常遇到这样的场景,统计一个字符串中所有的字符总共出现过几次:

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

    let mut letters = HashMap::new();

    // 统计每个字符串出现的参数使用  s.chars()
    for ch in s.split_whitespace() {
        let counter = letters.entry(ch).or_insert(0);
        *counter += 1;
    }

    println!("{:#?}", letters);
}

运行打印:

{
    "world": 2,
    "wonderful": 1,
    "hello": 1,
}

split_whitespace 方法返回一个由空格分隔 s 值子 slice 的迭代器。or_insert 方法返回这个键的值的一个可变引用(&mut V)。这里我们将这个可变引用储存在 counter 变量中,所以为了赋值必须首先使用星号(*)解引用 counter。这个可变引用在 for 循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则。

  • 原创
  • 学分: 3
  • 分类: Rust
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
125 订阅 31 篇文章

0 条评论

请先 登录 后评论
木头
木头
0xC020...10cf
江湖只有他的大名,没有他的介绍。