常见集合
Rust 标准库中包含一系列被称为 集合(collections)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。每种集合都有着不同功能和成本,而根据当前情况选择合适的集合,这是一项应当逐渐掌握的技能。
- vector 允许我们一个挨着一个地储存一系列数量可变的值
- 字符串(string)是字符的集合。我们之前见过
String
类型,不过在本章我们将深入了解。- 哈希 map(hash map)允许我们将值与一个特定的键(key)相关联。这是一个叫做 map 的更通用的数据结构的特定实现。
对于标准库提供的其他类型的集合,请查看文档。
使用 Vector 储存列表
Vec<T>
,也被称为 vector。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。
//创建一个新的空 vector,可以调用 Vec::new 函数
let v: Vec<i32> = Vec::new();
//使用 vec! 宏,根据提供的值来创建一个新的 vector
let v = vec![1, 2, 3];
//更新vector
//对于新建一个 vector 并向其增加元素,可以使用 push 方法
let mut v = Vec::new();//任何变量,如果想要能够改变它的值,必须使用 mut 关键字使其可变
v.push(5);
v.push(6);
v.push(7);
v.push(8);
//读取 vector 的元素
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];//索引语法
println!("The third element is {third}");
let third: Option<&i32> = v.get(2);//get方法
match third {
Some(third) => println!("The third element is {third}"),
None => println!("There is no third element."),
}
let does_not_exist = &v[100];//报错
let does_not_exist = v.get(100);//返回None
//遍历 vector 中的元素
let v = vec![100, 32, 57];
for i in &v {
println!("{i}");
}
//使用枚举来储存多种类型
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
使用字符串储存 UTF-8 编码的文本
Rust 的核心语言中只有一种字符串类型:字符串 slice
str
,它通常以被借用的形式出现,&str
。 字符串 slices,它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说,由于字符串字面值被储存在程序的二进制输出中,因此字符串字面值也是字符串 slices。字符串(
String
)类型由 Rust 标准库提供,而不是编入核心语言,它是一种可增长、可变、可拥有、UTF-8 编码的字符串类型。
//新建字符串 很多 Vec 可用的操作在 String 中同样可用,事实上 String 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据
let mut s = String::new();
let data = "initial contents";
let s = data.to_string();
// 该方法也可直接用于字符串字面值:
let s = "initial contents".to_string();
let s = String::from("initial contents");
//更新字符串
let mut s = String::from("foo");
s.push_str("bar");//通过 push_str 方法来附加字符串 slice,从而使 String 变长
s.push('l'); //push 方法被定义为获取一个单独的字符作为参数,并附加到 String 中 注意是单引号
//使用 + 运算符
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
//format! 宏拼接字符串
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{s1}-{s2}-{s3}"); // tic-tac-toe
//遍历字符串的方法
for c in "Зд".chars() {//对 “Зд” 调用 chars 方法会将其分开并返回两个 char 类型的值
println!("{c}"); // 0->З 1->д
}
for b in "Зд".bytes() {// bytes 方法返回每一个原始字节
println!("{b}");//0->208 1->151 2->208 3->180
}
使用 Hash Map 储存键值对
HashMap<K, V>
类型储存了一个键类型K
对应一个值类型V
的映射。它通过一个 哈希函数(hashing function)来实现映射,决定如何将键和值放入内存中哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引
像 vector 一样,哈希 map 将它们的数据储存在堆上
类似于 vector,哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。
//新建一个哈希map
use std::collections::HashMap;//首先 use 标准库中集合部分的 HashMap
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50)
//访问哈希 map 中的值
let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0);//可以通过 get 方法并提供对应的键来从哈希 map 中获取值
//遍历
for (key, value) in &scores {
println!("{key}: {value}");
}
//哈希 map 和所有权
//对于像 i32 这样的实现了 Copy trait 的类型,其值可以拷贝进哈希 map。对于像 String 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者
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 和 field_value 不再有效,
//更新哈希 map 当我们想要改变哈希 map 中的数据时,必须决定如何处理一个键已经有值了的情况。可以选择完全无视旧值并用新值代替旧值。可以选择保留旧值而忽略新值,并只在键 没有 对应值时增加新值。或者可以结合新旧两值。
//覆盖一个值
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 则被覆盖了。
//只在键没有对应值时插入键值对
//我们经常会检查某个特定的键是否已经存在于哈希 map 中并进行如下操作:如果哈希 map 中键已经存在则不做任何操作。如果不存在则连同值一块插入。为此哈希 map 有一个特有的 API,叫做 entry,它获取我们想要检查的键作为参数。entry 函数的返回值是一个枚举,Entry,它代表了可能存在也可能不存在的值。
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);//Entry 的 or_insert 方法在键对应的值存在时就返回这个值的可变引用,如果不存在则将参数作为新值插入并返回新值的可变引用。
scores.entry(String::from("Blue")).or_insert(50);
println!("{scores:?}");
//根据旧值更新一个值
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:?}");//{"hello": 1, "world": 2, "wonderful": 1}
哈希函数
HashMap
默认使用一种叫做 SipHash 的哈希函数,它可以抵御涉及哈希表(hash table)1 的拒绝服务(Denial of Service, DoS)攻击。然而这并不是可用的最快的算法,不过为了更高的安全性值得付出一些性能的代价。如果性能监测显示此哈希函数非常慢,以致于你无法接受,你可以指定一个不同的 hasher 来切换为其它函数。hasher 是一个实现了BuildHasher
trait 的类型