写点什么

30 天拿下 Rust 之 HashMap

作者:希望睿智
  • 2024-05-28
    安徽
  • 本文字数:4839 字

    阅读完需:约 16 分钟

30天拿下Rust之HashMap

💡 如果想阅读最新的文章,或者有技术问题需要交流和沟通,可搜索并关注微信公众号“希望睿智”。

概述

HashMap,被称为哈希表或散列表,是一种可以存储键值对的数据结构。它使用哈希函数将键映射到存储位置,以便可以快速检索和更新元素。这种数据结构在许多编程语言中都存在,而在 Rust 中,它被实现为 HashMap<K, V>。其中,K 表示键的类型,V 表示值的类型。HashMap 以哈希表为基础实现,允许我们在常数平均时间复杂度内完成插入、删除和查找操作。

HashMap 的创建

Rust 标准库中提供了 std::collections::HashMap<K, V>,这是一个关联数组或映射。其中,K 是键类型,必须实现 Eq 和 Hash traits 以确保键的唯一性和能够进行哈希计算。V 是值类型,可以是任何 Rust 支持的类型。

每个键都会通过哈希函数转化为一个索引,并以此存储对应的值,从而使得通过键快速定位到值成为可能。当两个不同的键通过哈希函数得到相同的索引时,会发生“哈希冲突”。此时,HashMap 会通过开放寻址法或者链地址法等策略来解决这个问题。

要使用 HashMap,必须先引入 std::collections::HashMap 模块。新建 HashMap,主要有以下几种方式。

1、使用 new 函数创建一个新的、空的 HashMap。

use std::collections::HashMap;
fn main() { // 创建一个空的HashMap,键类型为String,值类型为i32 let mut map_fruit: HashMap<String, i32> = HashMap::new(); // 插入一些键值对 map_fruit.insert("Lemon".to_string(), 66); map_fruit.insert("Apple".to_string(), 99); // 输出:{"Lemon": 66, "Apple": 99} println!("{:?}", map_fruit);}
复制代码

2、新建带有元素的 HashMap。通过传入一个键值对的集合(比如:数组、切片或迭代器),我们可以在创建 HashMap 的同时初始化它。这可以通过 collect 方法来实现,它通常与 vec!宏或数组字面量一起使用,以创建包含(key, value)元组的集合。在下面的示例代码中,我们首先创建了一个 HashMap。它的键是 String 类型,值是 i32 类型。然后,我们使用 vec!宏创建了一个包含三个(key, value)元组的向量,并使用 into_iter 方法将其转换为迭代器。最后,我们使用 collect 方法将其收集到一个 HashMap 中。

use std::collections::HashMap;
fn main() { let map_fruit: HashMap<String, i32> = vec![ ("Lemon".to_string(), 66), ("Apple".to_string(), 99)].into_iter().collect(); // 输出:{"Lemon": 66, "Apple": 99} println!("{:?}", map_fruit);}
复制代码

3、HashMap::from 是一个创建 HashMap 的便捷方法,主要用于从实现了 IntoIterator 特征且迭代器产出元组 (K, V) 的类型创建一个 HashMap。

use std::collections::HashMap;
fn main() { let pairs = [("Lemon".to_string(), 66), ("Apple".to_string(), 99)]; let map_fruit = HashMap::from(pairs);
// 输出:{"Lemon": 66, "Apple": 99} println!("{:?}", map_fruit);}
复制代码

4、使用 with_capacity 函数创建预先分配指定容量的 HashMap。注意:预设容量只是预留空间,实际使用的数量会根据插入的键值对自动增长。

use std::collections::HashMap;
fn main() { // 创建一个初始容量为5的HashMap let mut map_fruit: HashMap<String, i32> = HashMap::with_capacity(5); // 插入一些键值对 map_fruit.insert("Lemon".to_string(), 66); map_fruit.insert("Apple".to_string(), 99); // 输出:{"Lemon": 66, "Apple": 99} println!("{:?}", map_fruit);}
复制代码

HashMap 的访问

HashMap 是一个存储键值对的数据结构,并且可以通过键来快速检索值。为了访问 HashMap 中的值,我们可以使用 get 方法或 get_mut 方法,具体取决于是否需要获取值的可变引用。

1、get 方法用于获取与给定键相关联的值的不可变引用。如果键存在于 HashMap 中,get 将返回 Some(value),其中 value 是与该键相关联的值的引用。如果键不存在,它将返回 None。

use std::collections::HashMap;
fn main() { let mut map_fruit = HashMap::new(); map_fruit.insert("Lemon".to_string(), 66); map_fruit.insert("Apple".to_string(), 99);
// 访问存在的键 if let Some(value) = map_fruit.get("Apple") { println!("found value: {}", value); } else { println!("not found"); } // 访问不存在的键 if let Some(value) = map_fruit.get("Peach") { println!("found value: {}", value); } else { println!("not found"); }}
复制代码

2、如果我们需要获取值的可变引用以便修改它,则应该使用 get_mut 方法。与 get 方法类似,如果键存在于 HashMap 中,get_mut 将返回 Some(&mut value),其中 &mut value 是与该键相关联的值的可变引用。如果键不存在,它将返回 None。

use std::collections::HashMap;
fn main() { let mut map_fruit = HashMap::new(); map_fruit.insert("Lemon".to_string(), 66); map_fruit.insert("Apple".to_string(), 99);
// 访问存在的键 if let Some(value) = map_fruit.get_mut("Apple") { *value = 100; } else { println!("not found"); }
// 输出:{"Apple": 100, "Lemon": 66} println!("{:?}", map_fruit); // 访问不存在的键 if let Some(value) = map_fruit.get_mut("Peach") { println!("found value: {}", value); } else { println!("not found"); }}
复制代码

HashMap 的修改

1、插入新键值对。如果键不存在,使用 insert 方法将添加一个新的键值对。如果键已经存在,则会替换原有的值。

use std::collections::HashMap;
fn main() { // 创建一个空的HashMap,键类型为String,值类型为i32 let mut map_fruit: HashMap<String, i32> = HashMap::new(); // 插入一些键值对 map_fruit.insert("Lemon".to_string(), 66); map_fruit.insert("Apple".to_string(), 99); // 输出:{"Lemon": 66, "Apple": 99} println!("{:?}", map_fruit);}
复制代码

2、如果需要根据键是否存在来执行不同的操作(比如:只在键不存在时插入值,或者在键存在时更新值),可以使用 entry API。这提供了更细粒度的控制,并避免了不必要的查找。entry 方法会根据键是否存在返回一个 Entry 枚举;or_insert 方法会在键不存在时插入给定的值,并返回键的值的可变引用;and_modify 方法会修改现有的值。

use std::collections::HashMap;
fn main() { let mut map_fruit = HashMap::new(); map_fruit.insert("Lemon".to_string(), 66); map_fruit.insert("Apple".to_string(), 99);
// 使用entry API插入新的键值对,并修改值为原来的2倍 map_fruit.entry("Peach".to_string()).or_insert(256); map_fruit.entry("Peach".to_string()).and_modify(|v| *v *= 2); // 输出: {"Peach": 512, "Lemon": 66, "Apple": 99} println!("{:?}", map_fruit);}
复制代码

3、使用 remove 方法可以移除指定键的键值对。当我们调用 remove 方法并传入一个键时,如果该键存在于 HashMap 中,它会返回与该键相关联的值,并从 HashMap 中删除该键值对。如果键不存在,会返回 None。

use std::collections::HashMap;
fn main() { let mut map_fruit = HashMap::new(); map_fruit.insert("Lemon".to_string(), 66); map_fruit.insert("Apple".to_string(), 99);
// 尝试删除并获取"Lemon"的值,会成功 if let Some(value) = map_fruit.remove("Lemon") { println!("{} removed", value); } else { println!("not found"); }
// 尝试删除并获取"Peach"的值,会失败 if let Some(value) = map_fruit.remove("Peach") { println!("{} removed", value); } else { println!("not found"); }
// 输出: {"Apple": 99} println!("{:?}", map_fruit);}
复制代码

HashMap 的遍历

在 Rust 中,我们可以使用多种方式来遍历 HashMap,包括:遍历所有的键、遍历所有的值、同时遍历键和值。

1、遍历所有的键。我们可以使用 keys()方法来获取一个包含所有键的迭代器,并遍历它们。

use std::collections::HashMap;
fn main() { let pairs = [("Lemon".to_string(), 66), ("Apple".to_string(), 99)]; let map_fruit = HashMap::from(pairs);
// 分别输出:Lemon Apple for key in map_fruit.keys() { println!("{}", key); }}
复制代码

2、遍历所有的值。我们可以使用 values()方法来获取一个包含所有值的迭代器,并遍历它们。

use std::collections::HashMap;
fn main() { let pairs = [("Lemon".to_string(), 66), ("Apple".to_string(), 99)]; let map_fruit = HashMap::from(pairs);
// 分别输出:99 66 for value in map_fruit.values() { println!("{}", value); }}
复制代码

3、同时遍历键和值。如果需要同时访问键和值,我们可以使用 iter()方法,它会返回一个包含键值对引用的迭代器。

use std::collections::HashMap;
fn main() { let pairs = [("Lemon".to_string(), 66), ("Apple".to_string(), 99)]; let map_fruit = HashMap::from(pairs);
// 分别输出:Apple: 99 Lemon: 66 for (key, value) in map_fruit.iter() { println!("{}: {}", key, value); }}
复制代码

4、遍历并修改值。如果需要遍历 HashMap 并修改其中的值,我们可以使用 iter_mut()方法,它会返回一个包含可变键值对引用的迭代器。注意:当使用 iter_mut()方法时,不能有其他对 HashMap 或其任何元素的可变引用。因为 Rust 的借用规则要求:在同一时间,变量只能有一个可变引用存在。

use std::collections::HashMap;
fn main() { let pairs = [("Lemon".to_string(), 66), ("Apple".to_string(), 99)]; let mut map_fruit = HashMap::from(pairs);
// 修改值为原来的10倍 for (key, value) in map_fruit.iter_mut() { *value *= 10; }
// 分别输出:Lemon: 660 Apple: 990 for (key, value) in map_fruit.iter() { println!("{}: {}", key, value); }}
复制代码

HashMap 的所有权

在 Rust 中,HashMap 对插入其中的键值对的所有权规则,遵循 Rust 语言的核心所有权原则。这意味着,当我们将一个值放入 HashMap 时,会根据值的类型决定所有权如何转移。

1、复制所有权。对于实现了 Copy 特征的类型(比如:整数、浮点数等基本类型),插入 HashMap 时不会发生所有权转移,而是进行值的复制。

use std::collections::HashMap;
fn main() { let mut map = HashMap::new(); let number: i32 = 66; map.insert("Lemon", number); // 这里仍可以继续使用number,因为复制了一份 println!("{}", number);}
复制代码

2、转移所有权。如果插入到 HashMap 中的值是不可复制的类型(比如:String 或自定义结构体),那么当调用 insert 方法时,该值的所有权会被转移给 HashMap。这意味着,原变量将不再有效,并且不能再被使用。

use std::collections::HashMap;
fn main() { let mut map = HashMap::new(); let peach = String::from("Peach"); // peach的所有权转移到了HashMap中 map.insert("Fruit", peach); // 这里访问peach会导致编译错误,因为它已经不再拥有所有权 // println!("{}", peach);}
复制代码

3、引用所有权。如果想要存储指向数据的引用,而不是数据本身,可以使用引用类型(比如:&str 或 &T)。但是,引用的生命周期必须与引用的对象保持一致,确保在整个引用存在期间,对象也依然有效。

use std::collections::HashMap;
fn main() { let text = String::from("World"); let mut map = HashMap::new(); map.insert("Hello", &text); // text必须一直有效,因为HashMap持有对它的引用}
复制代码


发布于: 刚刚阅读数: 4
用户头像

希望睿智

关注

一起学习,一起成长,一起进步! 2024-05-21 加入

中国科学技术大学毕业,在客户端、运营级平台、Web开发、嵌入式开发、深度学习、人工智能、音视频编解码、图像处理、流媒体等多个领域具备实战开发经验和技术积累,共发表发明专利十余项,软件著作权几十项。

评论

发布
暂无评论
30天拿下Rust之HashMap_hashmap_希望睿智_InfoQ写作社区