我必须立刻押注 Rust 之十🎲:Vec

96 阅读5分钟

Vec 的基础概念

Vec<T> 是一个动态大小的集合,可以存储任意类型 T 的元素。与固定大小的数组不同,Vec 可以根据需要增长或缩小。它是一个非常强大且灵活的集合类型,广泛用于 Rust 中的数据存储。

存储方式Vec 会动态管理内存(栈上存储指向堆上数据的指针),元素在堆上分配。 所有权Vec 拥有其元素的所有权,这意味着当 Vec 被销毁时,所有的元素也会被销毁。

Vec API

创建 Vec

  • 使用关联函数创建 Vec

    // 创建一个空的 Vec
    let v: Vec<i32> = Vec::new();
    // 容器的初始大小
    let v = Vec::with_capacity(10);
    // from 函数创建
    let v = Vec::from([1, 2, 3]);
    
  • 使用宏创建 Vec

    // 创建一个空的 Vec
    let v = vec![];
    // 创建一个包含初始元素的 Vec
    let v = vec![1, 2, 3];
    

内存管理与性能

在 Rust 中,Vec 使用堆分配内存,并具有智能的内存管理策略。其容量(capacity)是指其当前为元素预分配的内存大小。Vec 会根据需要增加或减少容量。

容量与长度

  • len(): 获取 Vec 中当前元素的个数。

  • capacity(): 获取 Vec 当前容量的大小,即预分配的内存空间。

  • reserve(): 向 Vec 请求额外的空间,这样可以避免在添加元素时频繁重新分配内存。

  • shrink_to_fit(): 将 Vec 的容量收缩为当前实际使用的内存大小。

重新分配内存

Vec 超过当前容量时,它会自动扩大内存,通常会将容量增加到原容量的两倍。这种扩容策略有助于减少重复的内存分配操作。

let mut v = Vec::new();
v.push(1);  // 会进行一次内存分配
v.push(2);  // 继续在当前分配的内存中插入元素
v.reserve(100);  // 请求预分配更多内存(避免后续的重复分配)
println!("Capacity: {}", v.capacity());  // 输出容量

Vec 的增长策略

默认情况下,Vec 会每次扩展容量时,按 2x 的增长因子进行扩展。这样能够有效减少扩展次数,提高性能。

常用操作与方法

添加元素

  • push(T):向 Vec 的末尾添加一个元素。

    let mut v = Vec::new();
    v.push(1);
    v.push(2);
    
  • extend<I>(iterable):将一个迭代器中的所有元素追加到 Vec 末尾。

    let mut v = vec![1, 2];
    v.extend(vec![3, 4, 5].into_iter());
    println!("{:?}", v);  // 输出: [1, 2, 3, 4, 5]
    

删除元素

  • pop():删除并返回 Vec 末尾的元素。

    let mut v = vec![1, 2, 3];
    let last = v.pop();
    println!("{:?}", last);  // Some(3)
    
  • remove(index):按索引删除元素。

    let mut v = vec![1, 2, 3];
    let removed = v.remove(1);  // 删除索引为1的元素
    println!("Removed: {}", removed);  // 2
    println!("{:?}", v);  // [1, 3]
    
  • clear():删除所有元素,Vec 会被清空,但不改变它的容量。

    let mut v = vec![1, 2, 3];
    v.clear();
    println!("{:?}", v);  // []
    

索引访问

  • 使用索引访问元素。注意,Rust 会在访问时进行边界检查。

    let v = vec![10, 20, 30];
    println!("{}", v[1]);  // 20
    
  • 使用 get() 方法,安全访问元素,返回 Option<T>,可以避免索引越界错误。

    let v = vec![10, 20, 30];
    if let Some(value) = v.get(2) {
        println!("{}", value);  // 30
    } else {
        println!("Out of bounds");
    }
    

遍历元素

  • 使用 for 循环遍历:

    let v = vec![1, 2, 3];
    for val in &v {
        println!("{}", val);  // 1 2 3
    }
    
  • 使用 iter() 方法返回一个迭代器:

    let v = vec![1, 2, 3];
    v.iter().for_each(|x| println!("{}", x));  // 1 2 3
    

其他操作

  • 合并 Vec

    let mut v1 = vec![1, 2];
    let mut v2 = vec![3, 4];
    v1.append(&mut v2);  // 将 v2 的内容移动到 v1 中
    println!("{:?}", v1);  // [1, 2, 3, 4]
    println!("{:?}", v2);  // []
    
  • 切片操作

    let v = vec![10, 20, 30, 40];
    let slice = &v[1..3];  // 取索引1到2的元素 [20, 30]
    println!("{:?}", slice);  // [20, 30]
    

切片与数组

  • 与切片的转换Vec 可以轻松转换为切片,并且切片的生命周期通常短于 Vec 的生命周期。

    let v = vec![1, 2, 3];
    let slice: &[i32] = &v[0..2];  // 从 Vec 转为切片
    
  • 从切片创建 Vec: 切片可以通过 .to_vec() 方法转换为 Vec

    let slice: &[i32] = &[1, 2, 3];
    let v = slice.to_vec();
    println!("{:?}", v);  // [1, 2, 3]
    

迭代与操作

Rust 提供了高效的迭代器模式,可以让你对 Vec 进行各种高效操作:

映射与过滤

  • 使用 iter().map() 来映射元素。

    let v = vec![1, 2, 3];
    let squared: Vec<i32> = v.iter().map(|x| x * x).collect();
    println!("{:?}", squared);  // [1, 4, 9]
    
  • 使用 iter().filter() 来过滤元素。

    let v = vec![1, 2, 3, 4, 5];
    let even: Vec<i32> = v.iter().filter(|&&x| x % 2 == 0).cloned().collect();
    println!("{:?}", even);  // [2, 4]
    

折叠操作

  • 使用 fold()Vec 中的元素折叠成一个值。
    let v = vec![1, 2, 3, 4];
    let sum = v.iter().fold(0, |acc, &x| acc + x);
    println!("{}", sum);  // 10
    

所有权管理

  • Vec 会遵循所有权规则,当 Vec 被移动时,它的所有权会转移,原来的变量将无法继续使用。
    let v = vec![1, 2, 3];
    let v2 = v;  // v 的所有权转移给 v2
    println!("{:?}", v);  // 错误:use of moved value
    
    
  • 通过引用和借用,你可以避免移动元素,从而提高性能和内存效率。
    let v = vec![1, 2, 3];
    let v_ref = &v;  // 只借用,v 不会移动
    println!("{:?}", v_ref);  // [1, 2, 3]
    

应用场景

  • 动态存储数据(如读取文件行、网络数据包)。
  • 数组替代品,用于需要动态调整大小的场景。
  • 配合迭代器链式操作,进行数据转换和处理。