0%

Rust 学习笔记:第8章 常用的集合

Vector

  • Vec<T>,叫做 vector
    • 由标准库提供
    • 可存储多个值
    • 只能存储相同类型的数据
    • 值在内存中连续存放

创建 Vector

  • Vec::new 函数

    1
    2
    3
    fn main() {
    let v: Vec<i32> = Vec::new();
    }
  • 使用初始值创建 Vec<T>,使用 vec!

    1
    2
    3
    fn main() {
    let v = vec![1, 2, 3];
    }

    更新 Vector

  • 向 Vector 添加元素,使用 push 方法

1
2
3
4
fn main() {
let mut v: Vec<i32> = Vec::new();
v.push(1);
}

删除 Vector

  • 与任何其它 struct 一样,当 Vector 离开作用域后
    • 它就被清理掉了
    • 它所有元素也被清理掉了

读取 Vector 的元素

  • 两种方式可以引用 Vector 里的值
    • 索引
    • get 方法
1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 索引
let third: &i32 = &v[2];
println!("The third element is {}", third);

// get 方法,返回 Option<T>
match v.get(2) {
Some(third) => println!("The third element is {}", third);
None => println!("There is not thord element"),
}
}

遍历 Vector 中的值

  • for 循环
1
2
3
4
5
6
7
fn main() {
let v = vec![100, 32, 57];
// 如果不使用引用的话,执行完for循环之后,Vector 所有权就会被移动
for i in &v {
println!("{}", i);
}
}
1
2
3
4
5
6
7
8
9
10
11
fn main() {
let mut v = vec![100, 32, 57];
for i in &mut v {
// * 解引用
*i += 50;
}

for i in v {
println!("{}", i);
}
}

Vector + Enum 例子

使用 enum 来存储多种数据类型

  • Enum 的变体可以附加不同类型的数据
  • Enum 的变体定义在同一个 enum 类型下
1
2
3
4
5
6
7
8
9
10
11
12
13
enum SpreadsheetCell {
Int(i32),
Flodat(f64),
Text(String),
}

fn main() {
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
}

String

Rust 开发者经常会被字符串困扰的原因

  • Rust 倾向于暴露可能的错误
  • 字符串数据结构复杂
  • UTF-8

字符串是什么

  • Byte 的集合
  • 提供了一些方法
    • 将 byte 解析成文本
  • Rust 的核心语言层面,只有一个字符串类型,字符串切片 str(或&str)
  • 字符串切片:对存储在其他地方、UTF-8 编码的字符串的引用
    • 字符串字面值:存储在二进制文件中,也是字符串切片
  • String 类型
    • 来自标准库,而不是核心语言
    • 可增长、可修改、可拥有所有权
    • 采用 UTF-8 编码

通常说的字符串是指

  • String 和 &str
    • 标准库里用的多
    • UTF-8 编码

其它类型的字符串

  • Rust 的标准库还包含了其它的字符串类型,例如:OsString、OsStr、CString、CStr
    • String vs Str 后缀:拥有或借用的变体
    • 可存储不同编码的文本或在内存中以不同的形式展现
  • Libary crate (第三方库)针对存储字符串可提供更多的选项

创建一个新的字符串(String)

  • 很多 Vec<T> 的操作都可用于 String

  • String::new 函数

    1
    2
    3
    fn main() {
    let mut s = String::new();
    }
  • 使用初始值来创建 String

    • to_string() 方法,可用于实现 Display trait 的类型,包括字符串字面值

      1
      2
      3
      4
      5
      6
      fn main() {
      let data = "initial contents";
      let s = data.to_string();

      let s1 = "initial contents".to_string();
      }
    • String::from 函数,从字面值创建 String

      1
      2
      3
      fn main() {
      let s = String::from("initial contents");
      }

更新 String

  • push_str() 方法:把一个字符串切片附加到 String

    1
    2
    3
    4
    5
    fn main() {
    let mut s = String::from("foo");
    s.push_str("bar");
    println!("{}", a);
    }
  • push() 方法:把单个字符附加到 String

    1
    2
    3
    4
    fn main() {
    let mut s = String::from("lo");
    s.push('l');
    }
  • +:连接字符串

    • 使用了类似这个签名的方法 fn add(self, s: &str) -> String{...}

      • 标准库中的 add 方法使用了泛型
      • 只能把 &str 添加到 String
      • 解引用强制转换(deref coeracion)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      fn main() {
      let s1 = String::from("hello, ");
      let s2 = String::from("world");

      // +前面是String,后面是引用,不能将两个String添加
      let s3 = s1 + &s2;

      println!("{}", s3);
      // 因为拼接字符串时采用String,所以s1不可用
      // println!("{}", s1);
      println!("{}", s2);
      }
  • format!:链接多个字符串

    • println!() 方法一样,但返回的是字符串

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      fn main() {
      let s1 = String::from("tic");
      let s2 = String::from("tac");
      let s2 = String::from("toe");

      // let s3 = s1 + "-" + &s2 + "-" + &3;
      // println!("{}", s3);

      let s = format!("{}-{}-{}", s1, s2, s3);
      println!("{}", s);
      }

      String 按索引访问

  • 按索引语法访问 String 的某部分,会报错

    1
    2
    3
    4
    5
    fn main() {
    let s = String::from("tic");
    // 报错
    let s1 = s[0];
    }
  • Rust 的字符串不支持索引语法访问

String 内部表示

  • String 是对 Vec<u8> 的包装
    • len() 方法返回 String 的字符数

String 的字符索引并不一定能对上有效的字符

1
2
3
4
fn main() {
let len = String::from("hola").len();
println!("{}", len);
}

字节、标量值、字形簇

Bytes,Scalar Values,Grapheme Clusters

  • Rust 有三种看待字符串的方式
    • 字节
    • 标量值
    • 字形簇(最接近所谓的 “字母”)
  • Rust 不允许对 String 进行索引的最后一个原因
    • 索引操作应消耗一个常量时间O(1)
    • 而 String 无法保证:需要遍历所有内容,来确定有多少个合法的字符

切割 String

  • 可以使用 [] 和一个范围来创建字符串的切片
    • 必须谨慎使用
    • 如果切割时跨越了字符边界,程序就会 panic
1
2
3
4
5
6
7
8
9
10
11
fn main() {
let hello = "你好世界";
let s = &hello[0..6];

// 一个汉字3个字节,如果切割0到5程序就会panic
// 这就是切割跨越了字符边界
// let s = &hello[0..5];

// 你好
println!("{}", s);
}

遍历 String 的方法

  • 对于标量值:chars() 方法遍历
  • 对于字节:bytes() 方法
  • 对于字形簇:很复杂,标准库未提供

String 不简单

  • Rust 选择将正确处理 String 数据作为所有 Rust 程序的默认行为
    • 程序员必须在处理 UTF-8 数据之前投入更多的精力
  • 可防止在开发后期处理涉及非 ASCII 字符的错误

HashMap<K, V>

  • 键值对的形式存储数据,一个键(key)对应一个值(Value)
  • Hash 函数:决定如何在内存中存储 K 和 V
  • 适用场景:通过 K(任何类型)来寻找数据,而不是通过索引
  • HashMap 用的较少,不在 Prelude 中
  • 标准库对其支持较少,没有内置的宏来创建 HashMap
  • 数据存储在 heap 上
  • 同构的,一个 HashMap 中
    • 所有的 K 必须时同一种类型
    • 所有的 V 必须时同一种类型

创建 HashMap

  • 创建空 HashMap:new() 函数
  • 添加数据:insert() 方法
1
2
3
4
5
6
7
8
use std::collections::HashMap;

fn main() {
// 使用 HashMap::new() 来创建 HashMap 时,需要使用限定 K V类型
// 不限定,可以使用 insert 插入时,编译器会自动推导
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Blue"), 10);
}

collect 方法创建 HashMap

  • 在元素类型为 Tuple 的 Vector 上使用 collect 方法,可以组建一个 HashMap
    • 要求 Tuple 有两个值:一个作为 K,一个作为 V
    • collect 方法可以把数据整合成很多种集合类型,包括 HashMap
      • 返回值需要显式指定类型
1
2
3
4
5
6
7
8
use std::collections::HashMap;

fn main() {
let teams = vec![String.from("Blue"), String::from("yellow")];
let intial_scores = vec![10, 50];
// teams 作为 k,intial_scores 作为 V
let scores: HashMap<_, _> = teams.iter(intial_scores.iter()).collect();
}

HashMap 和所有权

  • 对于实现了 Copy trait 的类型(例如 i32),值会被复制到 HashMap 中

  • 对于拥有所有权的值(例如 String),值会被移动,所有权会转移给 HashMap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    use std::collections::HashMap;

    fn main() {
    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 所有权交给了 HashMap
    println!("{}, {}", field_name, field_value);
    }
  • 如果将值的引用插入到 HashMap ,值本身不会移动

    • 在 HashMap 有效的期间,被引用的值必须保持有效
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    use std::collections::HashMap;

    fn main() {
    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(&field_name, &field_value);

    println!("{}, {}", field_name, field_value);
    }

访问 HashMap 中的值

  • get 方法
    • 参数:K
    • 返回:Option<&V>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::collections::HashMap;

fn main() {
let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 10);

let team_name = String::from("Blue");
let score = scores.get(&team_name);

match score {
Some(s) => println!("{}", s),
None => prtinln!("team not exist"),
};
}

遍历 HashMap

  • for 循环
1
2
3
4
5
6
7
8
9
10
11
12
use std::collections::HashMap;

fn main() {
let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 10);

for (k, v) in &scores {
println!("{}: {}", k, v);
}
}

更新 HashMap

  • HashMap 大小可变
  • 每个 K 同时只能对应一个 V
  • 更新 HashMap 中的数据
    • K 已经存在,对应一个 V
      • 替换现有的 V
      • 保留现有的 V,忽略新的 V
      • 合并现有的 V 和新的 V
    • K 不存在
      • 添加一对新的 KV

替换现有的 V

如果向 HashMap 插入一对 KV,然后再插入同样的 K,但是不同的 V,那么原来的 V 会被替换掉

1
2
3
4
5
6
7
8
9
10
use std::collections::HashMap;

fn main() {
let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);

println!("{:?}", scores);
}

K不对应任何值,才插入 V

  • entry 方法:检查指定的 K 是否对应一个 V
    • 参数为 K
    • 返回 enum Entry:代表值是否存在
  • Entry 的 or_insert() 方法
    • result:
      • 如果 K 存在,返回对应 V 的一个可变引用
      • 如果 K 不存在,将方法参数作为 K 的新值插进去,返回这个值的可变引用
1
2
3
4
5
6
7
8
9
10
11
12
use std::collections::HashMap;

fn main() {
let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);

scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);

println!("{:?}", scores);
}

基于现有V来更新V

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::collections::HashMap;

fn main() {
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);
}

Hash 函数

  • 默认情况下,HashMap 使用加密功能强大的 Hash 函数,可以抵抗拒绝服务(Dos)攻击
    • 不是可用的最快的 Hash 算法
    • 但具有更好安全性
  • 可以指定不同的 hasher 来切换成另一个函数
    • hasher 是实现 BuildHasher trait 的类型