HashMap创建,插入,读取,修改
和动态数组Vec
一样,哈希表(HashMap)
也是Rust
内置的集合类型之一,同属std::collections
模块下。HashMap<K, V>
类型储存了一个键类型 K
对应一个值类型 V
的映射。它通过一个 哈希函数(hashing function)
来实现映射,决定如何将键和值放入内存中。
哈希 map
可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector
那样通过索引。
map
可以使用 new
创建一个空的 HashMap
,并使用 insert
增加元素,我们记录两支队伍的分数,分别是蓝队和黄队。蓝队开始有 10 分而黄队开始有 50 分:
use std::collections::HashMap;
fn main() {
// 声明
let mut m = HashMap::new();
// 插入
m.insert(String::from("Blue"), 10);
m.insert(String::from("Yellow"), 50);
}
注意必须首先 use
标准库中集合部分的 HashMap
。在这三个常用集合中,HashMap
是最不常用的,所以并没有被 prelude
自动引用。标准库中对 HashMap
的支持也相对较少。
像 vector
一样,哈希 map
将它们的数据储存在堆上,这个 HashMap
的键类型是 String
而值类型是 i3
2。类似于 vector
,哈希 map
是同质的:所有的键必须是相同类型,值也必须都是相同类型。
map
中的值可以通过 get
方法并提供对应的键来从哈希map
中获取值:
use std::collections::HashMap;
fn main() {
// 声明
let mut m = HashMap::new();
// 插入
m.insert(String::from("Blue"), 10);
m.insert(String::from("Yellow"), 50);
let m1 = m.get(&String::from("Blue")).copied().unwrap_or(0);
}
这里,m1
是与蓝队分数相关的值,应为 10
。get
方法返回 Option<&V>
,如果某个键在哈希 map
中没有对应的值,get
会返回 None
。程序中通过调用 copied
方法来获取一个 Option<i32>
而不是 Option<&i32>
,接着调用 unwrap_or
在 m1
中没有该键所对应的项时将其设置为零。
利用get
的返回判断元素是否存在:
use std::collections::HashMap;
fn main() {
// 声明
let mut m = HashMap::new();
// 插入
m.insert(String::from("Blue"), 10);
m.insert(String::from("Yellow"), 50);
if let Some(m1) = m.get(&String::from("Blue")) {
println!("{}", m1);
} else {
println!("无结果");
}
}
可以使用与vector
类似的方式来遍历哈希 map
中的每一个键值对,也就是 for
循环:
use std::collections::HashMap;
fn main() {
// 声明
let mut m = HashMap::new();
// 插入
m.insert(String::from("Blue"), 10);
m.insert(String::from("Yellow"), 50);
for (k, v) in &m {
println!("{k}: {v}");
}
}
这会以任意顺序打印出每一个键值对:
Yellow: 50
Blue: 10
map
和所有权对于像 i32
这样的实现了Copy trait
的类型,其值可以拷贝进哈希 map
。对于像 String
这样拥有所有权的值,其值将被移动而哈希 map
会成为这些值的所有者:
use std::collections::HashMap;
fn main() {
// 声明
let mut m = HashMap::new();
let field_k = String::from("color");
let field_v = String::from("Blue");
m.insert(field_k, field_v);
// 这里 field_k 和 field_v 不再有效,
// 尝试使用它们看看会出现什么编译错误!
}
当 insert
调用将 field_k 和 field_v 移动到哈希 map
中后,将不能使用这两个绑定。
map
当我们想要改变哈希 map
中的数据时,必须决定如何处理一个键已经有值了的情况。可以选择完全无视旧值并用新值代替旧值。可以选择保留旧值而忽略新值,并只在键 没有
对应值时增加新值。或者可以结合新旧两值。
如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换:
use std::collections::HashMap;
fn main() {
let mut m = HashMap::new();
m.insert(String::from("Blue"), 10);
m.insert(String::from("Blue"), 25);
println!("m {:#?}", m);
}
这会打印出 {"Blue": 25}
。原始的值 10
则被覆盖了。
我们经常会检查某个特定的键是否已经存在于哈希 map
中并进行如下操作:如果哈希 map
中键已经存在则不做任何操作。如果不存在则连同值一块插入。
为此哈希map
有一个特有的API
,叫做 entry
,它获取我们想要检查的键作为参数。entry
函数的返回值是一个枚举,Entry
,它代表了可能存在也可能不存在的值:
use std::collections::HashMap;
fn main() {
let mut m = HashMap::new();
m.insert(String::from("Blue"), 10);
m.entry(String::from("Yellow")).or_insert(50);
m.entry(String::from("Blue")).or_insert(50);
println!("{:#?}", m);
}
运行:
{
"Blue": 10,
"Yellow": 50,
}
Entry
的 or_insert
方法在键对应的值存在时就返回这个值的可变引用,如果不存在则将参数作为新值插入并返回新值的可变引用。
另一个常见的哈希map
的应用场景是找到一个键对应的值并根据旧的值更新它,经常遇到这样的场景,统计一个字符串中所有的字符总共出现过几次:
use std::collections::HashMap;
fn main() {
let s = String::from("hello world wonderful world");
let mut letters = HashMap::new();
// 统计每个字符串出现的参数使用 s.chars()
for ch in s.split_whitespace() {
let counter = letters.entry(ch).or_insert(0);
*counter += 1;
}
println!("{:#?}", letters);
}
运行打印:
{
"world": 2,
"wonderful": 1,
"hello": 1,
}
split_whitespace
方法返回一个由空格分隔 s
值子 slice
的迭代器。or_insert
方法返回这个键的值的一个可变引用(&mut V)
。这里我们将这个可变引用储存在 counter
变量中,所以为了赋值必须首先使用星号(*
)解引用 counter
。这个可变引用在 for
循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!