|
|
@ -4,7 +4,7 @@
|
|
|
|
> <br>
|
|
|
|
> <br>
|
|
|
|
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
|
|
|
|
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
|
|
|
|
|
|
|
|
|
|
|
|
最后要介绍的常用集合类型是**哈希 map**(*hash map*)。`HashMap<K, V>`类型储存了一个键类型`K`对应一个值类型`V`的映射。它通过一个**哈希函数**(*hashing function*)来实现映射,它决定了如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表或者关联数组,仅举几例。
|
|
|
|
最后介绍的常用集合类型是 **哈希 map**(*hash map*)。`HashMap<K, V>` 类型储存了一个键类型 `K` 对应一个值类型 `V` 的映射。它通过一个**哈希函数**(*hashing function*)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表或者关联数组,仅举几例。
|
|
|
|
|
|
|
|
|
|
|
|
哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能得到他们的得分。
|
|
|
|
哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能得到他们的得分。
|
|
|
|
|
|
|
|
|
|
|
@ -23,9 +23,8 @@ scores.insert(String::from("Blue"), 10);
|
|
|
|
scores.insert(String::from("Yellow"), 50);
|
|
|
|
scores.insert(String::from("Yellow"), 50);
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
注意必须首先`use`标准库中集合部分的`HashMap`。在这三个常用集合中,这个是最不常用的,所以并不包含在被 prelude 自动引用的功能中。标准库中对哈希 map 的支持也相对较少;例如,并没有内建的用于构建的宏。
|
|
|
|
注意必须首先 `use` 标准库中集合部分的 `HashMap`。在这三个常用集合中,`HashMap` 是最不常用的,所以并没有被 prelude 自动引用。标准库中对 `HashMap` 的支持也相对较少,例如,并没有内建的构建宏。
|
|
|
|
|
|
|
|
像 vector 一样,哈希 map 将他们的数据储存在堆上,这个 `HashMap` 的键类型是 `String` 而值类型是 `i32`。同样类似于 vector,哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。
|
|
|
|
就像 vector 一样,哈希 map 将他们的数据储存在堆上。这个`HashMap`的键类型是`String`而值类型是`i32`。同样类似于 vector,哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
另一个构建哈希 map 的方法是使用一个元组的 vector 的 `collect` 方法,其中每个元组包含一个键值对。`collect` 方法可以将数据收集进一系列的集合类型,包括 `HashMap`。例如,如果队伍的名字和初始分数分别在两个 vector 中,可以使用 `zip` 方法来创建一个元组的 vector,其中“Blue”与 10 是一对,依此类推。接着就可以使用 `collect` 方法将这个元组 vector 转换成一个 `HashMap`:
|
|
|
|
另一个构建哈希 map 的方法是使用一个元组的 vector 的 `collect` 方法,其中每个元组包含一个键值对。`collect` 方法可以将数据收集进一系列的集合类型,包括 `HashMap`。例如,如果队伍的名字和初始分数分别在两个 vector 中,可以使用 `zip` 方法来创建一个元组的 vector,其中“Blue”与 10 是一对,依此类推。接着就可以使用 `collect` 方法将这个元组 vector 转换成一个 `HashMap`:
|
|
|
|
|
|
|
|
|
|
|
@ -38,7 +37,7 @@ let initial_scores = vec![10, 50];
|
|
|
|
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
|
|
|
|
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这里`HashMap<_, _>`类型注解是必要的,因为可能`collect`进很多不同的数据结构,而除非显式指定 Rust 无从得知你需要的类型。但是对于键和值的参数来说,可以使用下划线而 Rust 可以根据 vector 中数据的类型推断出哈希 map 所包含的类型。
|
|
|
|
这里`HashMap<_, _>`类型注解是必要的,因为可能`collect`进很多不同的数据结构,而除非显式指定 Rust 无从得知你需要的类型。但是对于键和值的类型参数来说,可以使用下划线占位,而 Rust 能够根据 vector 中数据的类型推断出 `HashMap` 所包含的类型。
|
|
|
|
|
|
|
|
|
|
|
|
### 哈希 map 和所有权
|
|
|
|
### 哈希 map 和所有权
|
|
|
|
|
|
|
|
|
|
|
@ -75,7 +74,7 @@ let team_name = String::from("Blue");
|
|
|
|
let score = scores.get(&team_name);
|
|
|
|
let score = scores.get(&team_name);
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这里,`score`将会是与蓝队分数相关的值,而这个值将是`Some(10)`。因为`get`返回`Option<V>`所以结果被封装进`Some`;如果某个键在哈希 map 中没有对应的值,`get`会返回`None`。程序将需要采用第六章提到的方法中之一来处理`Option`。
|
|
|
|
这里,`score` 是与蓝队分数相关的值,应为 `Some(10)`。因为 `get` 返回 `Option<V>`,所以结果被装进 `Some`;如果某个键在哈希 map 中没有对应的值,`get` 会返回 `None`。这时就要用某种第六章提到的方法来处理 `Option`。
|
|
|
|
|
|
|
|
|
|
|
|
可以使用与 vector 类似的方式来遍历哈希 map 中的每一个键值对,也就是`for`循环:
|
|
|
|
可以使用与 vector 类似的方式来遍历哈希 map 中的每一个键值对,也就是`for`循环:
|
|
|
|
|
|
|
|
|
|
|
@ -101,8 +100,7 @@ Blue: 10
|
|
|
|
|
|
|
|
|
|
|
|
### 更新哈希 map
|
|
|
|
### 更新哈希 map
|
|
|
|
|
|
|
|
|
|
|
|
虽然键值对的数量是可以增长的,不过每个单独的键同时只能关联一个值。当你想要改变哈希 map 中的数据时,必须选择是用新值替代旧值,还是完全无视旧值。我们也可以选择保留旧值而忽略新值,并只在键**没有**对应一个值时增加新值。或者可以结合新值和旧值。让我们看看着每一种方式是如何工作的!
|
|
|
|
尽管键值对的数量是可以增长的,不过任何时候,每个键只能关联一个值。当你想要改变哈希 map 中的数据时,根据目标键是否有值以及值的更新策略分成多种情况,下面我们了解一下:
|
|
|
|
|
|
|
|
|
|
|
|
#### 覆盖一个值
|
|
|
|
#### 覆盖一个值
|
|
|
|
|
|
|
|
|
|
|
|
如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换。即便下面的代码调用了两次`insert`,哈希 map 也只会包含一个键值对,因为两次都是对蓝队的键插入的值:
|
|
|
|
如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换。即便下面的代码调用了两次`insert`,哈希 map 也只会包含一个键值对,因为两次都是对蓝队的键插入的值:
|
|
|
@ -160,11 +158,11 @@ for word in text.split_whitespace() {
|
|
|
|
println!("{:?}", map);
|
|
|
|
println!("{:?}", map);
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这会打印出`{"world": 2, "hello": 1, "wonderful": 1}`,`or_insert`方法事实上会返回这个键的值的一个可变引用(`&mut V`)。这里我们将这个可变引用储存在`count`变量中,所以为了赋值必须首先使用星号(`*`)解引用`count`。这个可变引用在`for`循环的结尾离开作用域,这样所有这些改变都是安全的并被借用规则所允许。
|
|
|
|
这会打印出`{"world": 2, "hello": 1, "wonderful": 1}`,`or_insert`方法事实上会返回这个键的值的一个可变引用(`&mut V`)。这里我们将这个可变引用储存在`count`变量中,所以为了赋值必须首先使用星号(`*`)解引用`count`。这个可变引用在`for`循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则。
|
|
|
|
|
|
|
|
|
|
|
|
### 哈希函数
|
|
|
|
### 哈希函数
|
|
|
|
|
|
|
|
|
|
|
|
`HashMap`默认使用一个密码学上是安全的哈希函数,它可以提供抵抗拒绝服务(Denial of Service, DoS)攻击的能力。这并不是现有最快的哈希函数,不过为了更好的安全性带来一些性能下降也是值得的。如果你监控你的代码并发现默认哈希函数对你来说非常慢,可以通过指定一个不同的 *hasher* 来切换为另一个函数。hasher 是一个实现了`BuildHasher` trait 的类型。第十章会讨论 trait 和如何实现他们。你并不需要从头开始实现你自己的 hasher;crates.io 有其他人分享的实现了许多常用哈希算法的 hasher 的库。
|
|
|
|
`HashMap`默认使用一种密码学安全的哈希函数,它可以抵抗拒绝服务(Denial of Service, DoS)攻击。然而并不是最快的,不过为了更高的安全性值得付出一些性能的代价。如果性能监测显示此哈希函数非常慢,以致于你无法接受,你可以指定一个不同的 *hasher* 来切换为其它函数。hasher 是一个实现了`BuildHasher` trait 的类型。第十章会讨论 trait 和如何实现他们。你并不需要从头开始实现你自己的 hasher;crates.io 有其他人分享的实现了许多常用哈希算法的 hasher 的库。
|
|
|
|
|
|
|
|
|
|
|
|
## 总结
|
|
|
|
## 总结
|
|
|
|
|
|
|
|
|
|
|
|