将具有关联值的键存储在哈希 Map 中
我们的最后一个常见集合是哈希映射。该类型使用哈希来存储 type 键到 type 值
函数,它决定了如何将这些键和值放入内存中。
许多编程语言都支持这种数据结构,但它们通常
使用不同的名称,例如 hash、map、object、hash table、dictionary 或 associative array,仅举几例。HashMap<K, V>
K
V
当您想通过使用索引来查找数据时,哈希映射非常有用,因为 您可以使用 vectors,但使用可以是任何类型的 key。例如 在游戏中,您可以在哈希图中跟踪每支球队的分数,其中 每个键是团队的名称,值是每个团队的分数。给定一个团队 name 中,您可以检索其 score。
在本节中,我们将介绍哈希映射的基本 API,但还有更多好东西
隐藏在标准库定义的函数中。
与往常一样,请查看标准库文档以获取更多信息。HashMap<K, V>
创建新的哈希 Map
创建空哈希映射的一种方法是使用 .在示例 8-20 中,我们跟踪了两个团队的分数,他们的
名称为 Blue 和 Yellow。蓝队开始时有 10 分,而
黄色球队从 50 开始。new
insert
fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); }
请注意,我们需要首先从 collections 部分
标准库。在我们的三个常见系列中,这个系列最少
经常使用,因此它不包括在引入范围的功能中
自动在 Prelude 中。哈希 map 从
标准库;例如,没有内置的宏来构造它们。use
HashMap
就像向量一样,哈希 map 将其数据存储在堆上。这有
type 的键和类型的值。与向量一样,哈希映射也是
homogeneous:所有 key 必须具有相同的类型,并且所有值
必须具有相同的类型。HashMap
String
i32
访问哈希 Map 中的值
我们可以通过向方法提供 hash map 的 key 来从 hash map 中获取一个值,如示例 8-21 所示。get
fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); let team_name = String::from("Blue"); let score = scores.get(&team_name).copied().unwrap_or(0); }
此处,将具有与 Blue 团队关联的值,而
result 将为 。该方法返回一个 ;如果没有
值将返回 。这个程序
通过调用来处理 以获取 an 而不是 an ,如果没有,则设置为零
具有键的条目。score
10
get
Option<&V>
get
None
Option
copied
Option<i32>
Option<&i32>
unwrap_or
score
scores
我们可以像
do with vectors,使用循环:for
fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); for (key, value) in &scores { println!("{key}: {value}"); } }
此代码将按任意顺序打印每对:
Yellow: 50
Blue: 10
哈希映射和所有权
对于实现 trait 的类型(如 ),将复制值
放入哈希 map 中。对于拥有的值(如 ),这些值将被移动,并且
哈希 map 将是这些值的所有者,如示例 8-22 所示。Copy
i32
String
fn main() { use std::collections::HashMap; let field_name = String::from("Favorite color"); let field_value = String::from("Blue"); let mut map = HashMap::new(); map.insert(field_name, field_value); // field_name and field_value are invalid at this point, try using them and // see what compiler error you get! }
我们不能使用变量,之后
它们已通过对 .field_name
field_value
insert
如果我们将对值的引用插入到哈希 map 中,则值不会被移动 放入哈希 map 中。引用指向的值必须对 at 有效 至少只要哈希 map 有效。我们将在 “验证引用 Lifetimes“部分 第 10 章.
更新 Hash Map
尽管键和值对的数量是可增长的,但每个唯一键都可以
一次只有一个值与之关联(但反之则不然:对于
例如,蓝队和黄队都可以将值存储在哈希 map 中)。10
scores
当您想更改哈希映射中的数据时,您必须决定如何 处理 key 已分配值的情况。您可以将 old value 替换为 new value,完全忽略 old value。你可以 保留旧值并忽略新值,仅在 key 还没有值。或者,您可以将旧值和 new 值。让我们看看如何执行这些作!
覆盖值
如果我们将一个 key 和一个值插入到 hash map 中,然后插入相同的 key
使用不同的值,则与该键关联的值将被替换。
即使示例 8-23 中的代码调用了两次,哈希 map 也会
仅包含一个键值对,因为我们要为 Blue 插入值
团队的密钥。insert
fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Blue"), 25); println!("{scores:?}"); }
此代码将打印 。的原始值为
覆盖。{"Blue": 25}
10
仅在 key 不存在时添加 key 和 value
检查哈希 map 中是否已经存在特定键是很常见的 替换为值,然后执行以下作:如果 key 确实存在于 哈希 map 时,现有值应保持原样;如果密钥 不存在,请插入它及其值。
哈希映射有一个特殊的 API 用于此,称为 API,它接受密钥 you
想要作为参数进行检查。该方法的返回值是一个枚举
called 表示可能存在也可能不存在的值。假设
我们想检查 Yellow 团队的 key 是否具有关联的值
与它。如果没有,我们想插入值 ,对于
蓝队。使用 API,代码如图 8-24 所示。entry
entry
Entry
50
entry
fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.entry(String::from("Yellow")).or_insert(50); scores.entry(String::from("Blue")).or_insert(50); println!("{scores:?}"); }
方法 on 被定义为返回对
相应键的值(如果存在),如果不存在,则为 IT
插入参数作为此键的新值,并返回一个 mutable
引用新值。这种技术比编写
logic 的 logic,此外,它与 Borrow Checker 配合得更好。or_insert
Entry
Entry
运行示例 8-24 中的代码将打印 .这
第一次调用 to 将插入 Yellow 团队的 key 和 value,因为 Yellow 团队还没有值。第二次调用 不会更改哈希 map,因为 Blue 团队已经拥有
价值。{"Yellow": 50, "Blue": 10}
entry
50
entry
10
根据旧值更新值
哈希 map 的另一个常见用例是查找 key 的值,然后
根据旧值更新它。例如,示例 8-25 显示了
计算每个单词在某个文本中出现的次数。我们使用哈希 map 和
单词作为键并增加值以跟踪我们
看到那个词。如果这是我们第一次看到一个单词,我们将首先插入
值 .0
fn main() { use std::collections::HashMap; 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:?}"); }
此代码将打印 。您可能会看到
以不同顺序打印的相同键值对:回想一下“访问哈希映射中的值”部分,该部分
迭代哈希 map 以任意顺序进行。{"world": 2, "hello": 1, "wonderful": 1}
该方法返回子切片的迭代器,以
空格,值为 。该方法返回一个 mutable
reference () 设置为指定键的值。在这里,我们存储了
可变引用,因此为了将该值赋值为
我们必须首先使用星号 () 取消引用。可变的
reference 在循环结束时超出范围,因此所有这些
更改是安全的,并且是借用规则允许的。split_whitespace
text
or_insert
&mut V
count
count
*
for
哈希函数
默认情况下,使用名为 SipHash 的哈希函数,该函数可以提供
抵御涉及哈希的拒绝服务 (DoS) 攻击
表HashMap
1.这不是最快的哈希算法
可用,但为了更好的安全性而付出了代价
性能是值得的。如果您分析代码并发现默认的
hash 函数太慢了,你可以切换到另一个函数
通过指定不同的 hasher 来执行。哈希器是实现 trait 的类型。我们将在第 10 章讨论 trait 以及如何实现它们。您不一定非得实施
从头开始您自己的哈希器;crates.io 具有其他 Rust 用户共享的库,这些库提供了实现许多
常见的哈希算法。BuildHasher
总结
向量、字符串和哈希映射将提供大量功能 在需要存储、访问和修改数据的程序中是必需的。以下是 您现在应该准备好解决的一些练习:
- 给定一个整数列表,使用一个向量并返回中位数(排序后, 中间位置的值)和 mode (出现次数最多的值 经常;哈希映射在这里会有所帮助)。
- 将字符串转换为 pig 拉丁语。每个单词的第一个辅音被移动到 单词的结尾 和 ay 被加上去,所以 first 变成了 irst-fay。的话 以元音开头的元音在结尾加上 hay (apple 变成 apple-hay)。请记住有关 UTF-8 编码的详细信息!
- 使用哈希映射和向量,创建一个文本界面以允许用户添加 员工姓名到公司中的某个部门;例如,“Add Sally to (将 Sally 添加到 工程“或”将 Amir 添加到 Sales”。然后让用户检索所有 部门中的人员或公司中按部门划分的所有人员,已排序 按字母顺序。
标准库 API 文档描述了 vectors、 strings、 哈希图对这些练习很有帮助!
我们正在研究更复杂的程序,在这些程序中,作可能会失败,因此 这是讨论错误处理的最佳时机。我们接下来会这样做!
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准