rust学习 -- 第九章 常用的容器类型

162 阅读6分钟

第九章 常用的容器类型

Vector

分配在堆内存上的数据类型,可以动态改变大小.与列表类似吧.只能存放相同类型的数据.

Vec<T>

  • 由标准库提供
  • 可存储多个值
  • 只能存储相同类型的数据
  • 值在内存中连续存放

创建Vector

  1. Vec::new

    fn main() {
        let v: Vec<i32> = Vec::new(); // 因为我们使用Vec::new()创建的是空的vector,无法进行类型推断,所以需要指定类型.指定类型是为了分配存储空间.
    }
    
  2. 使用初始值创建Vec<T>,使用vec!

    fn main() {
        let v = vec![1,2,3];  // 这里可以推断出类型
    }
    

更新Vector

使用push向vector中添加元素

fn main() {
    let mut v = Vec::new();
​
    v.push(1);  // 这里有上下文类型推断
    v.push(2);
    v.push(3);
}

删除Vector

和struct一样,当Vector离开作用域后,它就会被清理掉,它所有的元素也会被清理掉.

如果Vector里面存储了引用,就需要特殊的处理了

读取Vector中的元素

两种方式引用Vector中的值:(他们返回的是索引还是所有权??)

  1. 索引
  2. get方法(找不到就返回none)
    fn main() {
        let v = vec![1,2,3,4,5];
        let third: &i32 = &v[2];
        println!("The third element is {}", third);
    ​
        match v.get(2) {
            Some(third) => println!("The third element is {}", third),
            None => println!("There is no third element"),
        }
    }

索引与.get

get会返回Option, 当我们可以确保索引不会越界的时候, 就是用索引访问, 否则使用.get访问.

所有权和借用规则在Vector中同样适用:不能在同一作用域内同时拥有可变和不可变引用.这主要是为了防止在可变借用中,意外修改Vector后重新分配存储空间导致引用失效.

    fn main() {
        let mut v = vec![1,2,3,4,5];
        let first = &v[0];  // 不可变借用
        v.push(6);  // 可变借用
        println!("The first element is {}", first);
    }

遍历Vector

    fn main() {
        let v = vec![2,3,4];
        for i in &v {
            println!("{}", i);
        }
    }

    fn main() {
        let mut v = vec![2,3,4];
        for i in &mut v {
            *i += 20;  // 遍历修改
        }
    }

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

Enum的变体可以附加不同类型的数据

Enum的变体定义在同一个enum类型下.在编译时知道类型,就可以分配空间,并且知道数据支持哪些操作.

    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }
    ​
    fn main() {
        let now = vec![
            SpreadsheetCell::Int(3),
            SpreadsheetCell::Text(String::from("blue")),
            SpreadsheetCell::Float(10.12), // 三个单元格,每个单元格都是SpreadsheetCell中的一种变体
        ];
    }

使用特征对象

    trait IpAddr {
        fn display(&self);
    }
    ​
    struct V4(String);
    impl IpAddr for V4{
        fn display(&self) {
            println!("ipv4: {:?}", self.0)
        }
    }
    ​
    struct V6(String);
    impl IpAddr for V6 {
        fn display(&self) {
            println!("ipv6: {:?}", self.0)
        }
    }
    ​
    fn main() {
        let v: Vec<Box<dyn IpAddr>> = vec![  // 存入特征对象
            Box::new(V4("127.0.0.1".to_string())),
            Box::new(V6("::1".to_string())),
        ];
    ​
        for ip in v{
            ip.display();
        }
    }

HashMap

创建hashmap

创建Hashmap: new()函数

添加数据: insert()方法

use std::collections::HashMap;
​
fn main() {
    let mut scores: HashMap<String, i32> = HashMap::new();  // 没有加入值时需要指定类型 
}
use std::collections::HashMap;
​
fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 20); // 加入数据可以根据上下文推断出来类型
}
  • 由于hashmap用的比较少,不在prelude中,需要手动导入

  • 标准库对其支持较少,没有内置的宏来创建hashmap

  • 数据存储在heap上

  • hashmap是同构的,这是指一个hashmap中:

    • 所有的k必须是同一种类型
    • 所有的V必须是用一种类型

使用collect方法创建hashmap

在元素类型为tuple的vector上使用collect方法,可以组建一个hashmap:

  • tuple必须有两个值,一个作为k,一个作为v

  • collect 方法可以把数据整合成多种集合类型,包括hashmap

    • 返回值需要显式指明类型
use std::collections::HashMap;
​
fn main() {
    let teams = vec![String::from("Blue"), String::from("yellow")];
    let intial_scores = vec![10, 50];
​
    let scores: HashMap<_, _> = teams.iter().zip(intial_scores.iter()).collect(); // 这个例子和python中类似将两个vecter 合成一个tuple,然后使用collect返回一个集合,collect可以返回很多类型的集合,需要在接收的变量上指名类型.
    }
fn main () {
    use std::collections::HashMap;
    let teams_list = vec![
        ("一队".to_string(), 100),
        ("二队".to_string(), 10),
        ("三队".to_string(), 50),
    ];
    let teams_map: HashMap<_,_> = teams_list.into_iter().collect();
    println!("{:?}", teams_map)
}

hashmap和所有权

对于实现了copy trait 的类型如i32,值会被复制到hashmap中

对于拥有所有权的值如String,值会被移动,所有全会转移给hashmap

如果将值的引用插入到hashmap,值本身不会移动

use std::collections::HashMap;
​
fn main() {
    let field_name = String::from("favourite color");
    let field_value = String::from("bule");
​
    let mut map = HashMap::new();
    map.insert(field_name, field_value);  // 所有权发生了移动,我们不能再在之后使用他们
    println!("{}:{}", field_name, field_value);
​
    map.insert(&field_name, &field_value);  // 仅仅传递引用,不会产生所有权移动
    println!("{}:{}", field_name, field_value);
}

但是 在hashmap有效的期间,被引用的值必须保持有效.

访问hashmap中的值

get方法

参数k, 返回:Option<&V>

use std::collections::HashMap;
fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("yellow"), 10);
    scores.insert(String::from("blue"), 20);
​
    let team_name: String = String::from("blue");
    let score: Option<&i32> = scores.get(&team_name);  // 键是字符串引用, 返回引用match score {
        Some(s: &i32) => println!("{}", s),
        None => println!("team not exist"),
    }
}

遍历hashmap

使用for循环

fn main() {
    let mut scores = HashMap::new();
​
    scores.insert(String::from("blue"), 10);
    scores.insert(String::from("yellow"), 20);
​
    for (k, v) in &scores {
        println!("{}:{}", k,v);
    }
}

更新hashmap<k, v>

  • hashmap大小可变,每个时刻都可以改变他的大小.
  • 但是每个k同时只能对应一个v

更新hashmap中的数据,分为以下几种情况:

  1. k已经存在,对应一个V

    • 替换现有的v
    • 保留现有的V,忽略新的V
    • 合并现有的V和新的V
  2. k不存在

    • 添加一对k, v
    fn main() {
        let mut scores = HashMap::new();
    ​
        scores.insert(String::from("blue"), 10);
        scores.insert(String::from("blue"), 25);  // 替换了原有的值
        println!("{:?}", scores);
    ​
        scores.entry(String::from("blue")).or_insert(30); // 使用entry判断是否存在blue这个键.若不存在键则使用or_insert插入
    ​
        let count = scores.entry(String::from("blue")).or_insert(0);
        *count += 5;  // 解引用返回值,修改原有的值
    ​
        println!("{:?}", scores);
    }

entry方法: 检查指定的k是否对应一个V

  • 参数是k
  • 返回enum Entry:代表值是否存在

entryor_insert()方法返回:

  • 如果k存在,返回到对应的V的一个可变引用.
  • 如果k不存在,将方法参数作为k的新值插进去,返回到这个值的可变引用.

HashMap的内存布局

hashmap结构.png

hashmap在最开始时是没有空间分配的,随着哈希表不断插入数据, 它会以2的幂减一的方式增长, 最小是3, 当删除表中的数据时,原有的表大小不变,只有显式地调用 shrink_to_fit,才会让哈希表变小.

创建自己的HashMap

需要实现三个trait, Hash, PartialEq, Eq, 这三个都可以自动生成.

  • Hash 可以让数据结构计算哈希
  • PartialEq/Eq 让数据结构进行相等和不相等的比较. Eq 实现了比较的自反性(a == a)、对称性(a == b 则 b == a)以及传递性(a == b,b == c,则 a == c),PartialEq 没有实现自反性.
use std::{
    collections::{hash_map::DefaultHasher, HashMap},
    hash::{Hash, Hasher},
};
​
// 如果要支持 Hash,可以用 #[derive(Hash)],前提是每个字段都实现了 Hash
// 如果要能作为 HashMap 的 key,还需要 PartialEq 和 Eq
#[derive(Debug, Hash, PartialEq, Eq)]
struct Student<'a> {
    name: &'a str,
    age: u8,
}
​
impl<'a> Student<'a> {
    pub fn new(name: &'a str, age: u8) -> Self {
        Self { name, age }
    }
}
fn main() {
    let mut hasher = DefaultHasher::new();
    let student = Student::new("Tyr", 18);
    // 实现了 Hash 的数据结构可以直接调用 hash 方法
    student.hash(&mut hasher);
    let mut map = HashMap::new();
    // 实现了 Hash / PartialEq / Eq 的数据结构可以作为 HashMap 的 key
    map.insert(student, vec!["Math", "Writing"]);
    println!("hash: 0x{:x}, map: {:?}", hasher.finish(), map);
}