Vector 容器
概念
存储在堆内存的数组,可以动态扩容,写法 Vec<T>
- 标准库提供
- 可存储多个值
- 只能存储相同类型
- 在内存中连续存放
fn main() {
// 使用泛型显式声明Vec里存储的数据类型
let arr: Vec<i32> = Vec::new();
// 使用初始值创建,使用vec!宏, 编译器可以自动推导出Vec里存储的数据类型
let arr2 = vec![1, 2, 3];
}
插入数据使用 push 方法
fn main() {
let mut arr = Vec::new();
// 往Vec添加数据
arr.push(1);
}
读取值,使用索引或者 get 方法
fn main() {
let arr = vec![1, 2, 3, 4, 5];
let second = &arr[1];
println!("第二个元素是:{}", second); // 第二个元素是:2
// get方法传索引值,使用get时,如果索引值超出范围会比较安全,会走下面的None逻辑
match arr.get(1) {
Some(second) => println!("能拿到第二个元素:{}", second), //能拿到第二个元素:2
None => println!("拿不到第二个元素"),
}
}
Vec 的所有权借用
fn main() {
let mut arr = vec![1, 2, 3, 4, 5];
let first = &arr[0]; // 不可变的引用
// 这里会报错cannot borrow `arr` as mutable because it is also borrowed as immutable
arr.push(6);
println!("第一个元素的值是: {}", first);
}
为什么这里会报错呢,因为我们 first 借用的是第一个元素,下面 arr.push 新添加一个元素,又因为 Vec 在内存中是连续存放的,可能新添加进来的一个 6 会遇到地址不够的情况,那就得进行重新分配,得去找另一块连续地址的内存去存放,但是我们的 first 还是指向的原来的第 1 个元素的地址,就会出现问题了。
遍历 Vec 的值并修改里面的每一个元素
fn main() {
let mut arr = vec![1, 2, 3, 4, 5];
for i in &mut arr {
//解引用才能去修改值
*i += 1;
println!("遍历修改后的值是:{}", i);
}
}
// 遍历修改后的值是:2
// 遍历修改后的值是:3
// 遍历修改后的值是:4
// 遍历修改后的值是:5
// 遍历修改后的值是:6
如果想要在 Vec 中存储不同的数据类型,可以使用 enum
- enum 的变体可以附加不同的数据类型
- enum 的变体定义在同一个 enum 类型下
enum ArrType {
Int(i32),
Float(f32),
Text(String),
}
fn main() {
let arr = vec![
ArrType::Float(1.2),
ArrType::Int(10),
ArrType::Text(String::from("hello world"))
]
}
String
概念
Rust 中的字符串可能会比较让人困扰,在 Rust 中它并不简单
- Rust 倾向于暴露可能的错误
- 字符串数据结构复杂
- 使用了 UTF-8 编码
字符串是基于 byte 的集合,里面提供了一些方法能将 byte 解析为文本。
Rust 在核心语言层面只有一种字符串类型:字符串切片 str( &str)。它是对存储在其它地方、UTF-8 编码的字符串的引用。字符串字面值存储在二进制文件中。
标准库中的 String 类型:内容可增长、可修改、可获得所有权
通常所说的字符串是 String 或者&str
创建字符串
其实在这里之前我们就已经有过很多字符串的实践了,比如创建一个字符串String::new()
。
使用初始值来创建字符串的话可以使用to_string()
,可用于实现了 Display trait 的类型,包括字符串字面值。
fn main() {
//如果直接创建字面量字符串,它的类型是&str。
let str = "hello world";
//下面两种创建字符串的方法效果是一样的,可根据个人喜好选择写法。
//而使用to_string方法,可以将它转变为String类型
let str2 = "hello world".to_string();
//使用String::from来创建String类型的字符串。
let str3 = String::from("hello world");
}
更新 String
push_str 方法:把一个字符串切片附加到 String
push 方法:把单个字符附加到 String 中
+运算符:这个方法实际上使用了类似于 fn add(self, &str) -> String 的方法。标准库中的 add 方法是使用了泛型的,所以这里说是类型于,这个方法只能把&str 类型添加到 String 中,这里我们传入的&s2 是 String 的引用,这个方法使用解引用强制转换将 String 转换成了字符串切片&str,所以可以调用成功。s1 的所有权转移到 add 函数里,所以 add 函数执行完 s1 就失效了。
rustfn main() { let s1 = String::from("hello"); let s2 = String::from(" world"); let s3 = s1 + &s2; println!("{}", s3); //hello world println!("{}", s1); //s1不能再使用了 println!("{}", s2); //s2可以继续使用 }
format!宏:连接多个字符串
rustfn main() { let s1 = String::from("打"); let s2 = String::from("飞"); let s3 = String::from("鸡"); let s = format!("{}-{}-{}", s1, s2, s3); println!("{}", s); //打-飞-鸡 }
String 类型的索引访问
String 类型的内部实现其实就是对 Vec<u8>的包装。
String 类型不支持索引方式访问,比如 str[0]或者&str[0]
都不能对 str 字符串进行索引访问。因为索引操作的时间复杂度是 O(1),而 String 不能保证这个 O(1)的时间复杂度,因为它需要去遍历所有内容来确认有多少合法的字符。
Rust 中有三种看待字符串的方式:
- 字节
- 标量值(Unicode 标量值)
- 字形簇(最接近所谓的"字母"):Rust 标准库中没有实现,可以使用第三方库
fn main() {
let s = "中文";
//这里使用的是字节的概念
for i in s.bytes() {
println!("{}", i);
}
// 一个中文用三个字节表示
// 228
// 184
// 173
// 230
// 150
// 135
// 这里使用的是标量值的概念
for i in s.chars() {
println!("{}", i);
}
// 中
// 文
}
切割 String
这里之前其实也讲了,就是字符串切片。在多语言环境中会容易出现问题
fn main() {
let str = "asdfghjkl";
println!("{}", &str[..2]); //as
}
fn main() {
let str = "中文";
println!("{}", &str[..3]);
// 因为一个中文表示三个字节类似于(a,b,c)(a,b,c),切割不按照边界切的话就会编译出错
// 'byte index 4 is not a char boundary; it is inside '文' (bytes 3..6) of `中文`'
println!("{}", &str[0..4]); //这里编译报错panicked
}
HashMap
概念
HashMap<K, V>,K 是 key,V 是 value,类似于 JS 的 Map。以键值对的形式存储数据,数据存储在堆中,HashMap 是同构的,就是所有 K 必须是同种类型,所有 V 也必须是同种类型。
适用于使用 key 来寻找值
创建
- 因为 HashMap 用的比较少,所以不在 prelude 预导入模块中,所以需要我们手动引入
- 标准库对其支持较少,没有内置的宏来创建 HashMap
- 初始化没有数据时需要显式声明类型,不显式声明时可以使用 insert 方法来添加一条数据,会自动推导出 hashMap 的类型
use std::collections::HashMap;
fn main() {
let mut map: HashMap<String, i32> = HashMap::new();
// 或者
let mut map2 = HashMap::new();
map2.insert(String::from("0"), 100);
}
Collect 方法创建 HashMap
在元素类型为 Tuple 的 Vector 上使用 collect 方法,可以组建一个 HhashMap
- 要求 Tuple 有两个值,一个作为 Key,一个作为 Value
- collect 方法可以把数据整合成很多种数据集合类型,包括 HashMap,返回值需要显式声明
use std::collections::HashMap;
fn main() {
let colors = vec![String::from("red"), String::from("pink")];
let items = vec![10, 20];
// 遍历items和colors,调用zip方法创建一个元组,就是拉链的意思
// 这两个Vector像拉链一样拉形成了一个元组的数组,最后使用collect生成hashMap
// collect可以生成很多种数据结构,所以必须要在前面显式声明数据类型
// 两个下划线占位符,Rust能推导出Key和Value的数据类型
let map: HashMap<_, _> = items.iter().zip(colors.iter()).collect();
}
HashMap 和所有权
- 对于实现了 Copy trait 的类型(i32 等存放在栈中的基础数据类型),值会被复制到 HashMap 中
- 对于拥有所有权的值(如 String 等存在堆中的数据类型),值会被移动,所有权会转移给 HashMap
- 如果将值的引用插入到 HashMap 中,值本身不移动
use std::collections::HashMap;
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world");
let mut map = HashMap::new();
map.insert(s1, s2);
// 因为s1、s2的所有权已经转移到HashMap里了,所以这里的s1和s2就已经失效了
// borrow of moved value: `s1`,`s2`
// 如果上面改成将引用传入,那么s1和s2就不会失效
println!("{}, {}", s1, s2);
}
访问 HashMap 中的值
get(key: K) -> Option<&V>
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(String::from("red"), 10);
map.insert(String::from("pink"), 20);
let red = String::from("red");
let val = map.get(&red);
match val {
Some(s) => println!("{}", s),
None => println!("HashMap中没有该值"),
}
}
遍历
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(String::from("red"), 10);
map.insert(String::from("pink"), 20);
for (k, v) in &map {
println!("k: {} , v: {}", k, v);
}
}
// k: pink , v: 20
// k: red , v: 10
更新
- HashMap 大小可变
- 每个 K 只能对应一个 V
更新策略:
替换原有的 V:就是两次插入的键相同时,第二次插入的值会覆盖掉第一次的值
添加:HashMap 中没有 K,那么我们才去插入 V,我们需要使用 entry 方法(检查指定的 K 是否对应一个 V,参数为 K,返回值 enum Entry 代表值是否存在)
rustuse std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(String::from("red"), 10); map.insert(String::from("pink"), 20); // blue的键不存在时就插入100 map.entry(String::from("blue")).or_insert(100); // {"red": 10, "pink": 20, "blue": 100} println!("{:?}", map); }
基于现有的 K 更新 V:
rustuse std::collections::HashMap; fn main() { let text = "hello world today is a good day"; let mut map = HashMap::new(); // 按空格分割的遍历器 for word in text.split_whitespace() { // 判断遍历到的单词在Map里是否存在,不存在就插入0 // 这里的count得到的是插入值的引用 let count = map.entry(word).or_insert(0); //解引用然后将插入的值+1 *count += 1; } // {"day": 1, "is": 1, "world": 1, "hello": 1, "today": 1, "good": 1, "a": 1} println!("{:#?}", map); }
Hash 函数
默认情况下,HashMap 使用加密功能强大的 Hash 函数,可以抵挡拒绝服务 (Dos) 攻击。
- 它不是可用的最快的 Hash 算法
- 但具有更好的安全性
- 可以指定不同的 Hasher 来切换到另一个函数,Hasher 是实现 BuildHasher trait 的类型