【百例RUST - 011】简单键值对
第一章 创建键值对
第01节 方式一
案例代码 直接创建
rust
// 需要导入 HashMap 的包
use std::collections::HashMap;
fn main(){
// 创建一个空的 HashMap
let mut scores: HashMap<String, i32> = HashMap::new();
// 添加数据
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 20);
// 输出结果
println!("{:?}", scores);
}
// {"Blue": 10, "Red": 20}
第02节 方式二
案例代码 将键的容器 和 值的容器 进行组合
rust
// 需要导入 HashMap 的包
use std::collections::HashMap;
fn main(){
// 定义 Key 和 Value 的容器
let keys: Vec<String> = vec![String::from("Blue"), String::from("Red")];
let values: Vec<i32> = vec![10,20];
// 将两个容器, 结合在一起, 作为 键值对
let scores: HashMap<_,_> = keys.iter().zip(values.iter()).collect();
// 输出结果
println!("{:?}", scores);
}
// {"Blue": 10, "Red": 20}
第二章 读取数据
第01节 直接读取
案例代码
rust
// 需要导入 HashMap 的包
use std::collections::HashMap;
fn main(){
// 创建一个空的 HashMap
let mut scores: HashMap<String, i32> = HashMap::new();
// 添加数据
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 20);
// 定义需要获取的键
let target_key = String::from("Blue");
// 读取数据
let result: Option<&i32> = scores.get(&target_key);
// 结果进行匹配
match result {
None => println!("none"),
Some(x) => println!("x = {}", x),
}
}
// x = 10
第02节 遍历操作
案例代码
rust
// 需要导入 HashMap 的包
use std::collections::HashMap;
fn main(){
// 创建一个空的 HashMap
let mut scores: HashMap<String, i32> = HashMap::new();
// 添加数据
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 20);
// 遍历数据
for(key, value) in &scores{
println!("{}, {}" , key, value);
}
}
// Blue, 10
// Red, 20
需要注意的是:遍历出现的结果顺序是不固定的
第三章 插入数据
第01节 直接插入
案例代码
rust
// 需要导入 HashMap 的包
use std::collections::HashMap;
fn main(){
// 创建一个空的 HashMap
let mut scores: HashMap<String, i32> = HashMap::new();
// 添加数据
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 20);
scores.insert(String::from("Yellow"), 30);
println!("{:?}", scores);
// 直接插入数据
scores.insert(String::from("Black"), 40);
println!("{:?}", scores);
// 如果存在键, 则替换值
scores.insert(String::from("Red"), 50);
println!("{:?}", scores);
}
// {"Blue": 10, "Red": 20, "Yellow": 30}
// {"Yellow": 30, "Blue": 10, "Red": 20, "Black": 40}
// {"Yellow": 30, "Blue": 10, "Red": 50, "Black": 40}
第02节 不存在 则插入
当键不存在的情况下, 则插入数据,键存在,则不做插入的操作
rust
// 需要导入 HashMap 的包
use std::collections::HashMap;
fn main(){
// 创建一个空的 HashMap
let mut scores: HashMap<String, i32> = HashMap::new();
// 添加数据
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 20);
scores.insert(String::from("Yellow"), 30);
println!("{:?}", scores);
// 当键不存在的时候, 则进行插入的操作
scores.entry(String::from("Red")).or_insert(66);
println!("{:?}", scores);
// 当键不存在的时候, 则进行插入的操作
scores.entry(String::from("White")).or_insert(88);
println!("{:?}", scores);
}
// {"Blue": 10, "Red": 20, "Yellow": 30}
// {"Blue": 10, "Red": 20, "Yellow": 30}
// {"Yellow": 30, "Blue": 10, "Red": 20, "White": 88}
第03节 根据旧值 更新新值
案例代码
rust
// 需要导入 HashMap 的包
use std::collections::HashMap;
fn main(){
// 根据旧值来更新一个值, 统计一个字符串当中, 单词出现的次数。
let text = "hello world my worldview world";
// 将字符串, 存放到 HashMap 当中
let mut map: HashMap<_, _> = HashMap::new();
// 利用字符串的空格, 进行分割字符串
for element in text.split_whitespace() {
// 获取对应单词的位置, 如果存在, 则不会插入
let count = map.entry(element).or_insert(0);
// 修改当前出现的次数
*count += 1;
}
// 输出内容
println!("{:?}", map);
}
// {"worldview": 1, "world": 2, "hello": 1, "my": 1}
详细说明一下,上面的代码含义
1、迭代器的部分:
代码:
text.split_whitespace()
介绍:
当前会根据空白字符(空格、换行等)来切割字符串,返回一个迭代器。
在当前的例子当中, 会依次拿到数据: "hello", "world", "my", "worldview", "world"
2、核心魔法:
代码:
map.entery(element)
介绍:
这是 Rust 的 hashMap 当中非常强大且高效的 Entry API
它的作用: 检查 map 当中是否已经存在了 名为 element 的键
返回值: 它会返回一个名为 Entry 的枚举 Enum 代表 这个位置可能有人(Occupied), 也可能没有人 (Vacant)
3、保底操作:
代码:
.or_insert(0)
介绍:
这是连接在 entry() 后面的方法, 其逻辑如下:
A. 如果键存在: 直接返回指向该键对应值的 可变引用 (&mut V)
B. 如果键不存在: 将0存入到这个键的位置, 然后返回指向这个新存入的 0 的可变引用 (&mut V)
C. 重点: 无论键之前在不在, 执行完毕这一行之后, 变量 count 都会得到一个指向 HashMap 内部数值的 "指针" (可变引用)
4、更新值:
代码:
*count += 1
介绍:
A. 由于 count 是一个可变引用(类型为 &mut i32) 我们不能直接给他加1
B. 我们需要使用解引用符 * 星号 来找到这个引用背后的真实数据值, 然后对其进行加1的操作
C. 因为 count 指向的是 HashMap 内部的空间, 所以这里修改会直接反映在 map 当中
逻辑流程介绍一下
假设循环进行到了第二次, 遇见 "world" 的时候:
1、 split_whitespace 会吐出 "world"
2、 map.entry("world") 发现 "嘿, 这个单词已经在Map集合里面了, 它的值现在是1"
3、 .or_inster(0) 看到值已经存在了, 所以不会插入0, 而是直接返回了指向1的引用。
4、 *count += 1 通过引用把内存里的 1 改变成为 2
总结一下
这种写法, 它既安全而且高效。
1、安全: 避免了先检查键是否存在, 再插入值的 "先查后改" 可能导致的逻辑错误。
2、高效: 它只进行了一次哈希计算, 如果手动调用 contains_key 判断再 insert 则需要计算两次哈希, 性能较低