rust-by-practice/zh-CN/src/collections/hashmap.md

224 lines
6.8 KiB
Markdown
Raw Permalink Normal View History

# HashMap
2022-03-09 00:10:59 -06:00
`HashMap` 默认使用 `SipHash 1-3` 哈希算法,该算法对于抵抗 `HashDos` 攻击非常有效。在性能方面,如果你的 key 是中型大小的,那该算法非常不错,但是如果是小型的 key( 例如整数 )亦或是大型的 key ( 例如字符串 ),那你需要采用社区提供的其它算法来提高性能。
2022-03-08 02:30:50 -06:00
2022-03-09 00:10:59 -06:00
哈希表的算法是基于 Google 的 [SwissTable](https://abseil.io/blog/20180927-swisstables),你可以在[这里](https://github.com/abseil/abseil-cpp/blob/master/absl/container/internal/raw_hash_set.h)找到 C++ 的实现,同时在 [CppCon talk](https://www.youtube.com/watch?v=ncHmEUmJZf4) 上也有关于算法如何工作的演讲。
2022-03-08 02:30:50 -06:00
2022-03-09 00:10:59 -06:00
### 基本操作
2022-03-08 02:30:50 -06:00
1. 🌟🌟
```rust,editable
2022-03-09 00:10:59 -06:00
// 填空并修复错误
2022-03-08 02:30:50 -06:00
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert("Sunface", 98);
scores.insert("Daniel", 95);
scores.insert("Ashley", 69.0);
scores.insert("Katie", "58");
2022-03-09 00:10:59 -06:00
// get 返回一个 Option<&V> 枚举值
2022-03-08 02:30:50 -06:00
let score = scores.get("Sunface");
assert_eq!(score, Some(98));
if scores.contains_key("Daniel") {
2022-03-09 00:10:59 -06:00
// 索引返回一个值 V
2022-03-08 02:30:50 -06:00
let score = scores["Daniel"];
assert_eq!(score, __);
scores.remove("Daniel");
}
assert_eq!(scores.len(), __);
for (name, score) in scores {
println!("The score of {} is {}", name, score)
}
}
```
2. 🌟🌟
2022-03-08 02:30:50 -06:00
```rust,editable
use std::collections::HashMap;
fn main() {
let teams = [
("Chinese Team", 100),
("American Team", 10),
("France Team", 50),
];
let mut teams_map1 = HashMap::new();
for team in &teams {
teams_map1.insert(team.0, team.1);
}
2022-03-09 00:10:59 -06:00
// 使用两种方法实现 team_map2
// 提示:其中一种方法是使用 `collect` 方法
2022-03-08 02:30:50 -06:00
let teams_map2...
assert_eq!(teams_map1, teams_map2);
println!("Success!")
}
```
3. 🌟🌟
2022-03-08 02:30:50 -06:00
```rust,editable
2022-03-09 00:10:59 -06:00
// 填空
2022-03-08 02:30:50 -06:00
use std::collections::HashMap;
fn main() {
2022-03-09 00:10:59 -06:00
// 编译器可以根据后续的使用情况帮我自动推断出 HashMap 的类型当然你也可以显式地标注类型HashMap<&str, u8>
2022-03-08 02:30:50 -06:00
let mut player_stats = HashMap::new();
2022-03-09 00:10:59 -06:00
// 查询指定的 key, 若不存在时,则插入新的 kv 值
2022-03-08 02:30:50 -06:00
player_stats.entry("health").or_insert(100);
assert_eq!(player_stats["health"], __);
2022-03-09 00:10:59 -06:00
// 通过函数来返回新的值
2022-03-08 02:30:50 -06:00
player_stats.entry("health").or_insert_with(random_stat_buff);
assert_eq!(player_stats["health"], __);
let health = player_stats.entry("health").or_insert(50);
assert_eq!(health, __);
*health -= 50;
assert_eq!(*health, __);
println!("Success!")
}
fn random_stat_buff() -> u8 {
2022-03-09 00:10:59 -06:00
// 为了简单,我们没有使用随机,而是返回一个固定的值
2022-03-08 02:30:50 -06:00
42
}
```
2022-03-09 00:10:59 -06:00
### HashMap key 的限制
2022-03-09 00:10:59 -06:00
任何实现了 `Eq``Hash` 特征的类型都可以用于 `HashMap` 的 key包括:
2022-03-08 02:30:50 -06:00
2022-03-09 00:10:59 -06:00
- `bool` (虽然很少用到,因为它只能表达两种 key)
- `int`, `uint` 以及它们的变体,例如 `u8`、`i32` 等
- `String``&str` (提示: `HashMap` 的 key 是 `String` 类型时,你其实可以使用 `&str` 配合 `get` 方法进行查询
2022-03-09 00:10:59 -06:00
需要注意的是,`f32` 和 `f64` 并没有实现 `Hash`,原因是 [浮点数精度](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems) 的问题会导致它们无法进行相等比较。
2022-03-08 02:30:50 -06:00
2022-03-09 00:10:59 -06:00
如果一个集合类型的所有字段都实现了 `Eq``Hash`,那该集合类型会自动实现 `Eq``Hash`。例如 `Vect<T>` 要实现 `Hash`,那么首先需要 `T` 实现 `Hash`
2022-03-08 02:30:50 -06:00
2022-03-09 02:34:49 -06:00
4. 🌟🌟
2022-03-08 02:30:50 -06:00
```rust,editable
2022-03-09 00:10:59 -06:00
// 修复错误
// 提示: `derive` 是实现一些常用特征的好办法
2022-03-08 02:30:50 -06:00
use std::collections::HashMap;
struct Viking {
name: String,
country: String,
}
impl Viking {
fn new(name: &str, country: &str) -> Viking {
Viking {
name: name.to_string(),
country: country.to_string(),
}
}
}
fn main() {
2022-03-09 00:10:59 -06:00
// 使用 HashMap 来存储 viking 的生命值
2022-03-08 02:30:50 -06:00
let vikings = HashMap::from([
(Viking::new("Einar", "Norway"), 25),
(Viking::new("Olaf", "Denmark"), 24),
(Viking::new("Harald", "Iceland"), 12),
]);
2022-05-04 11:44:14 -05:00
// 使用 derive 的方式来打印 viking 的当前状态
2022-03-08 02:30:50 -06:00
for (viking, health) in &vikings {
println!("{:?} has {} hp", viking, health);
}
}
```
2022-03-09 00:10:59 -06:00
### 容量
2022-03-09 00:10:59 -06:00
关于容量,我们在之前的 [Vector](https://zh.practice.rs/collections/vector.html#容量) 中有详细的介绍,而 `HashMap` 也可以调整容量: 你可以通过 `HashMap::with_capacity(uint)` 使用指定的容量来初始化,或者使用 `HashMap::new()` ,后者会提供一个默认的初始化容量。
2022-03-08 02:30:50 -06:00
2022-03-09 00:10:59 -06:00
#### 示例
2022-03-08 02:30:50 -06:00
```rust,editable
use std::collections::HashMap;
fn main() {
let mut map: HashMap<i32, i32> = HashMap::with_capacity(100);
map.insert(1, 2);
map.insert(3, 4);
2022-03-09 00:10:59 -06:00
// 事实上,虽然我们使用了 100 容量来初始化,但是 map 的容量很可能会比 100 更多
2022-03-08 02:30:50 -06:00
assert!(map.capacity() >= 100);
2022-03-09 00:10:59 -06:00
// 对容量进行收缩你提供的值仅仅是一个允许的最小值实际上Rust 会根据当前存储的数据量进行自动设置,当然,这个值会尽量靠近你提供的值,同时还可能会预留一些调整空间
2022-03-08 02:30:50 -06:00
map.shrink_to(50);
assert!(map.capacity() >= 50);
2022-03-09 00:10:59 -06:00
// 让 Rust 自行调整到一个合适的值,剩余策略同上
2022-03-08 02:30:50 -06:00
map.shrink_to_fit();
assert!(map.capacity() >= 2);
println!("Success!")
}
```
2022-03-09 00:10:59 -06:00
### 所有权
2022-03-08 02:30:50 -06:00
对于实现了 `Copy` 特征的类型,例如 `i32`,那类型的值会被拷贝到 `HashMap` 中。而对于有所有权的类型,例如 `String`,它们的值的所有权将被转移到 `HashMap` 中。
2022-03-09 00:10:59 -06:00
2022-03-09 02:34:49 -06:00
5. 🌟🌟
2022-03-08 02:30:50 -06:00
```rust,editable
2022-03-09 00:10:59 -06:00
// 修复错误,尽可能少的去修改代码
// 不要移除任何代码行!
2022-03-08 02:30:50 -06:00
use std::collections::HashMap;
fn main() {
let v1 = 10;
let mut m1 = HashMap::new();
m1.insert(v1, v1);
println!("v1 is still usable after inserting to hashmap : {}", v1);
let v2 = "hello".to_string();
let mut m2 = HashMap::new();
2022-03-09 00:10:59 -06:00
// 所有权在这里发生了转移
2022-03-08 02:30:50 -06:00
m2.insert(v2, v1);
2022-03-08 02:30:50 -06:00
assert_eq!(v2, "hello");
2022-03-09 02:34:49 -06:00
println!("Success!")
2022-03-08 02:30:50 -06:00
}
```
2022-03-09 00:10:59 -06:00
### 三方库 Hash 库
2022-03-09 00:10:59 -06:00
在开头,我们提到过如果现有的 `SipHash 1-3` 的性能无法满足你的需求,那么可以使用社区提供的替代算法。
例如其中一个社区库的使用方式如下:
2022-03-08 02:30:50 -06:00
```rust
use std::hash::BuildHasherDefault;
use std::collections::HashMap;
2022-03-09 00:10:59 -06:00
// 引入第三方的哈希函数
2022-03-08 02:30:50 -06:00
use twox_hash::XxHash64;
let mut hash: HashMap<_, _, BuildHasherDefault<XxHash64>> = Default::default();
hash.insert(42, "the answer");
assert_eq!(hash.get(&42), Some(&"the answer"));
```
> 你可以在[这里](https://github.com/sunface/rust-by-practice/blob/master/solutions/collections/Hashmap.md)找到答案(在 solutions 路径下)