Rust权威指南之通用集合类型

523 阅读7分钟

简述

Rust标准库包含了一系列非常有用的被称为集合的数据结构。本篇文章我们主要介绍:动态数组、字符串和哈希映射。

一. 动态数组

动态数组:Vec<T>。动态数组允许你在单个数据结构中存储多个相同类型的值,这些值会彼此相邻地排布在内存中。动态数组非常合适在需要存储一系列相同类型值的场景中使用。

1.1. 创建数组

Rust创建动态数组有以下几个方法:

  • Vec::new():创建一个空动态数组
  • Vec::with_capacity(10):创建一个指定容量的动态数组
  • vec![]:创建一个持有初始值的数组
  • vec![n; m]:创建并初始化vec,共m个元素,每个元素都初始化为n
let vec: Vec<i32> = Vec::new(); // []
let vec1: Vec<i32> = Vec::with_capacity(10); // []
let vec2 = vec![1, 2, 3]; // [1,2,3]
let vec3 = vec![1; 5]; // [1,1,1,1,1]

1.2. 修改和访问

如果需要使vec可以修改,需要使用mut关键字。例子:

let mut vec = Vec::new(); // 此时可以不用标明数据类型,当存入数据的时候,编译器可以推到出数据类型。
vec.push(10);
vec.push(11);
vec[1] = 12;
println!("{:?}", vec); // 修改前 [10, 11]
vec[1] = 12; // 修改
println!("{:?}", vec); // 修改后 [10, 12]

如果访问数组的数据,可以使用索引来访问vec中的元素。索引越界之后,将在运行时panic报错。

let v = vec![5, 6, 7];
let idx: usize = 1
let i = v[idx]; // 5

再有如果我们想引用动态数组中元素可以使用引用和get方法:

let v = vec![5, 6, 7];
let i = v[1]; // i32
println!("{}", i); // 6
let j = &v[2]; // &i32
println!("{}", j); // 7

vecget方法有两种:

  • get:获取指定索引处的元素引用或范围内元素的引用,如果索引越界,返回None
  • get_mut:获取元素的可变引用或范围内元素的可变引用,如果索引越界,返回None
let mut v = vec![5, 6, 7];
let a = v.get(1);  // Option<&i32>
println!("{}", a.unwrap()); // 6
v.get_mut(1).map(|e| {
    *e = 10
});
v.get(1).map(|data| {
    println!("{}", data) // 10
});

1.3. 数组遍历

数组遍历可以使用for in

let v = vec![5, 6, 7];
for i in &v {
    println!("{}", i);
}

如果想在遍历的时候修改数组:

let mut v = vec![5, 6, 7];
for i in &mut v {
    *i += 50;
}

1.4. 枚举和数组

从上面的例子我们可以知道,Vec只能存储一种类型的数据,那么如何突破这点呢?此时就可以用枚举突破,上例子:

enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let v = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Float(20.23),
    SpreadsheetCell::Text(String::from("hello world")),
];

1.6. 数组常用方法

接着我们可以看一下,vec常用的一些方法:

方法描述
len()返回vec的元素数量
is_empty()vec是否为空
push()vec尾部插入元素
pop()删除并返回vec尾部的元素,为空则返回None
insert()在指定位置插入元素
remove()删除指定索引处的元素并返回被删除的元素,索引越界将panic报错退出
clear()清空vec
append()将另一个vec中所有元素追加移入vec中,移动后另一个vec为空
truncate()vec截断到指定长度,多与的元素被删除
retain()保留满足条件的元素,即删除不满足条件的元素
drain()删除vec中指定范围的元素,同时返回一个迭代该范围所有元素的迭代器
split_off()vec从指定索引处切分成两个vec,索引左边(不包括索引位置处)的元素保留在原vec中,索引右边(包括索引位置处)的元素在返回的vec

更多的方法操作可以看:www.rustwiki.org.cn/zh-CN/std/v…

二. 字符串

Rust中的字符串使用了UTF-8编码。Rust在语言核心部分只有一种字符串类型,那就是字符串切片(str),它通常以借用(&str)的形式出现。

字符串是比很多开发者所理解的更为复杂的数据结构。加上UTF-8的不定长编码等原因,Rust 中的字符串并不如其它语言中那么好理解。

2.1. 字符串创建

基本上我们可以使用:newto_string方法进行创建:

let a = "hello world"; // &str
let b = a.to_string(); // String
let c = String::from("hello world"); // String
let d = "hello world".to_string(); // String

2.2. 更新字符串

更新字符串有三种方法,一种是使用push方法,如下:

let mut a = "hello world".to_string();
a.push_str(" rust");
println!("{}", a); // hello world rust
a.push('6');
println!("{}", a); // hello world rust6

另一种是使用+运算符,这里可以看作是push_str函数

let s1 = String::from("hello ");
let s2 = String::from("world");
let s3 = s1 + &s2;
println!("{}", s3); // hello world

最后一种是使用format!宏:

let s1 = String::from("hello ");
let s2 = String::from("world");
let s3 = format!("{}{}", s1, s2);
println!("{}", s3); // hello world

2.3. 其他操作

字符串索引,我们下面先看一个例子:

let s1 = String::from("我爱祖国");
println!("{}", s1[1]); // error[E0277]: the type `String` cannot be indexed by `{integer}`

上面报错的原因是因为,UTF-8 是不定长编码,但是String的实现是基于 Vec<u8> 的封装的,数组中每一个元素都是一个字节,但UTF-8中每一个汉字(或字符)都可能由一到四个字节组成。接着我们看一下字符串分隔操作:

let s1 = String::from("hello world");
println!("{}", &s1[0..3]); // hel

最后看一下字符串遍历操作:

let s1 = String::from("hello world");
for char in s1.chars() {
    println!("{}", char)
}

2.4. 常用方法

更多的操作可以标准库文档:www.rustwiki.org.cn/zh-CN/std/s…

方法描述
new()创建一个新的字符串对象
to_string()将字符串字面量转换为字符串对象
replace()搜索并替换
as_str()提取包含整个 String 的字符串切片
push()将给定的 char 追加到该 String 的末尾
push_str()将给定的字符串切片追加到这个 String 的末尾
pop()从字符串缓冲区中删除最后一个字符并返回它
remove()从该 String 的字节位置删除 char 并将其返回
insert()在此 String 的字节位置插入一个字符
len()返回此 String 的长度,以字节为单位
is_empty()如果此 String 的长度为零,则返回 true,否则返回 false
clear()截断此 String,删除所有内容

三. 哈希映射

哈希映射,存储了K类型键到V类型值之间的映射关系。这个我们在Java、go里面也有这种数据类型。

具体操作可以看标准库文档:www.rustwiki.org.cn/zh-CN/std/c…

3.1. 创建哈希映射

下面演示使用new创建了一个空的HashMap,使用insertHashMap中添加元素。

let mut map = HashMap::new();
// let mut map = HashMap::with_capacity(10); // 设置指定容量的HashMap
map.insert(String::from("Blue"), 10);
map.insert(String::from("Red"), 100);

RustHashMap还提供form/into方法可以用来创建存在初始值的HashMap

let map1 = HashMap::from([
    ("a", 1),
    ("b", 2),
    ("c", 3),
]);
// 使用into方法需要指定类型,此时编译器会推导出map2的类型是HashMap<_: &str, _: i32>
let map2: HashMap<_, _> = [("a", 1), ("b", 2), ("c", 3), ].into();

另外也可以通过entry函数进行插入:

let mut map = HashMap::new();
let text = "hello world rust";
for ch in text.chars(){ 
    // 判断字符是否存在,如果不存在则将初始化数量为0, 此时counter的类型是&mut i32
    let counter = map.entry(ch).or_insert(0);
    // 存在的话增加数量 *counter是解引用
    *counter += 1;
}
println!("{:?}", map);

3.2. 访问和更新

Rust中哈希映射提供了多种方法访问哈希映射的键和值:

let mut map = HashMap::from([("a", 1), ("b", 2), ("c", 3)]);
// 访问key
map.keys().for_each(|key| {
    println!("{}", key)
});
// 访问value
map.values().for_each(|value| {
    println!("{}", value)
});

获取value的方法也可以通过get等方法,这些方法返回的都是Option类型

map.get("a").map(|data| { println!("{}", data) }); 
map.get_mut("a").map(|data| { *data = 10 }); // get_mut可以获取可变引用
map.get_key_value("a").map(|(key, value)| {
    println!("key: {}, value: {}", key.deref(), value)
}); // get_key_value 返回与提供的键相对应的键值

接着我们看一个删除/过滤的操作,

let mut map = HashMap::new();
let text = "hello world rust";
for ch in text.chars(){
    let counter = map.entry(ch).or_insert(0);
    *counter += 1;
}
// 删除前 => {'h': 1, 'l': 3, 'r': 2, 'o': 2, ' ': 2, 'd': 1, 't': 1, 'u': 1, 's': 1, 'e': 1, 'w': 1}
println!("删除前 => {:?}", map);
map.retain(|&k, v| { k != ' ' && *v > 1 });
// 删除后 => {'l': 3, 'r': 2, 'o': 2}
println!("删除后 => {:?}", map);

3.3. 常用方法

方法描述
capacity()获取容量
clear()清空
contains_key()是否包含key
drain()清除map,将所有键值对作为迭代器返回
drain_filter()
entry()map中获取给定键的对应项,以进行就地操作。
get/get_mut/get_key_value通过某一个key获取value
insert()插入
into_keys()创建一个消费迭代器,以任意顺序访问所有键。 调用后不能使用map
into_values()创建一个消费迭代器,以任意顺序访问所有值。 调用后不能使用map
is_empty()是否为空
keys()获取所有的key
len获取长度
remove()根据某一个key删除
remove_entry()map中删除一个键,如果该键以前在map中,则返回存储的键和值
retain()删除指定谓词的元素,可以看着条件过滤