【Rust】008-常用集合

【Rust】008-常用集合

文章目录

一、动态数组: Vec<T>

1、引入

在Java中,List接口是一个非常常用的数据结构接口,它定义了一组可以用于操作有序集合 的方法。ArrayListList接口的一个常见实现,提供了动态数组 的功能。通过ArrayList,我们可以方便地进行元素的添加、删除和访问

如果你熟悉Java的ArrayList,那么理解Rust的Vec<T>会相对容易。以下是从ListArrayList引出Rust的Vec<T>的一些要点:

  1. 动态数组的概念

    • 在Java中,ArrayList实现了List接口,是一个可以动态调整大小的数组。它通过在内部维护一个可变大小的数组来实现这一点,并在需要时自动扩展。
    • 在Rust中,Vec<T>是一个动态数组,类似于Java的ArrayList。它可以根据需要动态扩展和缩小,存储同一类型的元素。
  2. 创建和初始化

    • 在Java中,创建一个ArrayList通常是通过new ArrayList<>()
    • 在Rust中,创建一个Vec可以通过Vec::new()或者使用宏vec![]来初始化。
  3. 添加元素

    • ArrayList使用add()方法来添加元素。
    • Vec<T>使用push()方法来添加元素。
  4. 访问元素

    • ArrayList中,可以使用get(index)方法来访问元素。
    • Vec<T>中,可以使用索引语法vec[index]来访问元素。
  5. 删除元素

    • ArrayList提供了remove(index)方法来删除指定位置的元素。
    • Vec<T>提供了remove(index)方法来删除指定位置的元素。
  6. 性能和内存管理

    • ArrayList依赖于Java的垃圾回收机制来管理内存。
    • Vec<T>则利用Rust的所有权系统来自动管理内存,确保在不再需要时自动释放。
  7. 线程安全

    • ArrayList不是线程安全的,但Java提供了其他线程安全的集合类。
    • Vec<T>也不是线程安全的,需要在多线程环境中使用同步机制来保护。

2、三种常见初始化方式

第一种:初始化一个长度为零且不进行内存分配的数组

这种方式使用Vec::new()方法。此方法创建一个空的Vec,长度为零,并且初始时不分配额外的内存空间。

场景:这种方式适合在不确定具体容量需求的情况下使用,Rust会在需要时自动分配内存。

rust 复制代码
let v: Vec<String> = Vec::new();

第二种:初始化一个长度为零但提前分配好16个元素大小内存的数组

使用Vec::with_capacity(16)方法。这会创建一个空的Vec,长度为零,但预先分配了可以容纳16个元素的内存。

场景:这种方式适合当你知道大致需要多少容量时使用,可以减少内存重新分配的开销,提高性能。

rust 复制代码
let mut v: Vec<String> = Vec::with_capacity(16);

第三种:使用vec!宏创建数组并初始化元素

vec!宏是Rust中用于创建Vec的便捷方式,可以直接初始化Vec并填入元素。

场景:这种方式适合在需要立即初始化并填充数据的情况下使用,代码简洁直观。

rust 复制代码
let v = vec!["hello".to_string(), "world".to_string()];

番外:为什么要这么写""hello".to_string()",直接写"hello"如何?

当你直接写"hello"时,编译器会将其视为字符串切片类型&str。如果你需要一个String(例如在创建Vec<String>时),你必须将&str转换为String。这就是为什么使用"hello".to_string()的原因。to_string()方法会将一个字符串切片转换为一个String类型。

参考链接:Rust 中的字符串类型:&strStringhttps://blog.csdn.net/qq_29689343/article/details/137350142

其它写法

Rust 有类型推导的能力

rust 复制代码
fn this_also_works() {
    let mut v = Vec::new(); // 这里可以先不指定类型
    v.push("str");          // 在这里,编译器会通过元素的类型确定 v 的类型,为 Vec<&str>
}

直接指定泛型类型

rust 复制代码
fn this_also_works2() {
    let _ = Vec::<String>::new();
}

3、添加、访问与修改数组元素

通过.push方法添加一个元素,可以通过下标[i]的方式访问数组元素,如果i超过数组长度,程序直接 panic。

rust 复制代码
fn get_by_index() {
    let mut v = Vec::new();
    // 通过`.push`方法添加一个元素
    v.push(1);
    v.push(2);
    
    println!("{}", v[1]); // 输出 2
    
    println!("{}", v[2]); // 这里程序会直接退出
}

那有没有不会 panic 的方式 呢?有的,我们可以通过.get 的方法,该函数的返回的是Option<&T>,如果数组越界了,会返回 None。(不过因为会多一次检查,这种方式性能会差一点)。

rust 复制代码
fn get_by_index() {
    let mut v = Vec::new();
    v.push(1);
    v.push(2);
    
    println!("{:?}", v.get(1)); // 输出 Some(2)
    
    println!("{:?}", v.get(2)); // 输出 None
}

通过下标修改数组元素

rust 复制代码
fn modify_by_index() {
    let mut v = Vec::new();
    v.push(1);
    v.push(2);
    
    v[0] = 100;
    println!("{}", v[0]); // 会输出 100
}

4、删除元素

可以通.pop将数组的最后一个元素弹出来,如果数组为空,则返回 None

rust 复制代码
fn pop_elem() {
    let mut v = vec!["Hello", "Rust"];
    println!("{:?}", v.pop()); // 输出 Some("Rust")
}

还可以通过.remove移出指定下标 的元素,且该方法会保持数组元素原来的顺 序。也可以通过.swap_remove来移除,不过这个会将数组最后一个元素移到被删除的位置,不能保持数组元素原来的顺序。(第二种方式好奇怪!)。

rust 复制代码
fn main() {
    let mut v = vec!["1", "2", "3", "4", "5"];
    v.remove(1);
    println!("{:?}", v); // ["1", "3", "4", "5"]

    v.swap_remove(1);
    println!("{:?}", v); // ["1", "5", "4"]
}

5、遍历元素

遍历元素要特别注意所有权, for是会消耗变量的。

rust 复制代码
fn will_consume_v() {
    let v = vec!["Hello", "World"];
    for elem in v {
        println!("{elem}");
    }
    
    println!("{:?}", v); // 这行将会报错,因为我们在第三行的 for 已经将 v 给消耗掉了
}

为了避免被消耗掉v,我们可以对v取引用,这样拿到的elem则会是&T类型:

rust 复制代码
fn willnot_consume_v() {
    let v = vec!["Hello", "World"];
    for elem in &v {
        println!("{elem}");
    }
    
    println!("{:?}", v); // 这行会正常输出
}

如果想对元素进行修改,可以对v取可变引用

rust 复制代码
fn modify_elem() {
    let mut v = vec!["Hello", "World"];
    for elem in &mut v {
        // *elem 注意这个写法
        *elem = "elem"
    }
    
    println!("{:?}", v); // 这行会输出 ["elem", "elem"]
}

二、HashMap<K, V>

1、引入

如果你熟悉 Java 的 HashMap,并且想要在 Rust 中使用类似的功能,以下是一些关键点和步骤,可以帮助你顺利过渡:

  1. 创建 HashMap

    • 在 Java 中,你通常会这样创建一个 HashMap

      java 复制代码
      Map<String, Integer> map = new HashMap<>();
    • 在 Rust 中,你需要引入标准库的集合模块,然后创建一个 HashMap

      rust 复制代码
      use std::collections::HashMap;
      
      let mut map = HashMap::new();
  2. 插入键值对

    • Java 中的插入操作使用 put 方法:

      java 复制代码
      map.put("key", 1);
    • Rust 中使用 insert 方法:

      rust 复制代码
      map.insert("key", 1);
  3. 访问值

    • 在 Java 中,你可以使用 get 方法来访问值:

      java 复制代码
      Integer value = map.get("key");
    • Rust 中的 get 方法返回一个 Option,因为键可能不存在:

      rust 复制代码
      if let Some(&value) = map.get("key") {
          println!("Value: {}", value);
      }
  4. 迭代

    • Java 中可以使用 entrySet 来迭代:

      java 复制代码
      for (Map.Entry<String, Integer> entry : map.entrySet()) {
          System.out.println(entry.getKey() + ": " + entry.getValue());
      }
    • 在 Rust 中,你可以使用 iter 方法:

      rust 复制代码
      for (key, value) in &map {
          println!("{}: {}", key, value);
      }
  5. 删除键值对

    • Java 使用 remove 方法:

      java 复制代码
      map.remove("key");
    • Rust 也使用 remove 方法:

      rust 复制代码
      map.remove("key");
  6. 检查键的存在性

    • Java 中可以使用 containsKey 方法:

      java 复制代码
      if (map.containsKey("key")) {
          // do something
      }
    • 在 Rust 中,你可以使用 contains_key 方法:

      rust 复制代码
      if map.contains_key("key") {
          // do something
      }
  7. 注意事项

    • Rust 中的 HashMap 默认不是线程安全的。如果你需要在多线程环境中使用,可以考虑使用 MutexRwLock 来保护它。
    • Rust 的 HashMap 使用泛型,确保在编译时进行类型检查,避免了许多运行时错误。

2、两种常见初始化方式

第一种:使用 new

HashMap::new() 创建一个空的 HashMap,默认情况下具有一定的初始容量,但具体的容量可能会根据实现和编译器的不同而有所变化。

rust 复制代码
use std::collections::HashMap;

let mut map = HashMap::new();

第二种:使用 with_capacity

HashMap::with_capacity(capacity) 创建一个具有指定初始容量的 HashMap。这对于知道大概要插入多少元素的情况特别有用,可以减少在插入过程中可能发生的内存重新分配。

rust 复制代码
use std::collections::HashMap;

let mut map = HashMap::with_capacity(10);

3、对元素的增删改查与遍历

rust 复制代码
use std::collections::HashMap;

fn main() {
    // 创建一个新的 HashMap
    let mut map = HashMap::new();

    // ## 1、添加元素
    // 使用 insert 方法添加键值对
    map.insert("apple", 3);
    map.insert("banana", 5);
    map.insert("orange", 2);
    // 此时,map 包含了三个键值对:("apple", 3), ("banana", 5), ("orange", 2)

    // ## 2、访问元素
    // 使用 get 方法访问元素,get 返回的是 Option<&V>
    if let Some(&count) = map.get("apple") {
        println!("apple 的数量是:{}", count);
    } else {
        println!("apple 不存在于 map 中");
    }
    // 如果键存在,打印其对应的值;否则,说明键不存在

    // ## 3、修改元素
    // 直接使用 insert 方法可以修改元素的值
    map.insert("banana", 10);
    // 现在 "banana" 对应的值被修改为 10

    // 另一种修改方法是使用 entry API
    map.entry("orange").and_modify(|count| *count += 1);
    // 使用 and_modify 仅在键存在时修改其值,将 "orange" 的数量加 1

    // ## 4、删除元素
    // 使用 remove 方法删除键值对
    map.remove("apple");
    // "apple" 及其对应的值从 map 中被移除

    // ## 5、遍历元素
    // 使用迭代器来遍历 HashMap 中的所有键值对
    for (key, value) in &map {
        println!("{}: {}", key, value);
    }
    // 这将打印出剩余的键值对,格式为 "键: 值"
    
    // 为了不消耗掉所有权,可以用&map或者&mut map去遍历。不过相应的,遍历元素的类型也会变成引用。
}
相关推荐
奈斯。zs11 分钟前
yjs08——矩阵、数组的运算
人工智能·python·线性代数·矩阵·numpy
Melody205011 分钟前
tensorflow-dataset 内网下载 指定目录
人工智能·python·tensorflow
学步_技术12 分钟前
Python编码系列—Python抽象工厂模式:构建复杂对象家族的蓝图
开发语言·python·抽象工厂模式
Narutolxy1 小时前
Python 单元测试:深入理解与实战应用20240919
python·单元测试·log4j
Amo Xiang1 小时前
2024 Python3.10 系统入门+进阶(十五):文件及目录操作
开发语言·python
liangbm31 小时前
数学建模笔记——动态规划
笔记·python·算法·数学建模·动态规划·背包问题·优化问题
B站计算机毕业设计超人1 小时前
计算机毕业设计Python+Flask微博情感分析 微博舆情预测 微博爬虫 微博大数据 舆情分析系统 大数据毕业设计 NLP文本分类 机器学习 深度学习 AI
爬虫·python·深度学习·算法·机器学习·自然语言处理·数据可视化
羊小猪~~2 小时前
深度学习基础案例5--VGG16人脸识别(体验学习的痛苦与乐趣)
人工智能·python·深度学习·学习·算法·机器学习·cnn
waterHBO4 小时前
python 爬虫 selenium 笔记
爬虫·python·selenium
编程零零七5 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql