Rust面试题及详细答案120道(58-65)-- 集合类型

前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux... 。

前后端面试题-专栏总目录

文章目录

一、本文面试题目录

58. 简述Rust标准库中的主要集合类型(VecStringHashMap等)

Rust标准库提供了多种集合类型,用于存储和管理数据集合,它们都在堆上分配内存,且具有不同的特性和适用场景。主要集合类型包括:

  • Vec<T>(动态数组)

    • 原理:连续存储相同类型T的元素,支持动态扩容,内部基于Vec<u8>实现。
    • 特点:随机访问效率高(O(1)),适合存储有序、可重复的元素。
  • String(字符串)

    • 原理:UTF-8编码的可变字符串,内部本质是Vec<u8>,但保证数据符合UTF-8标准。
    • 特点:支持字符串拼接、修改,适合处理文本数据。
  • HashMap<K, V>(哈希表)

    • 原理:基于哈希函数存储键值对(K为键,V为值),通过键快速查找值。
    • 特点:插入和查询效率高(平均O(1)),但元素无序,K需实现HashEq trait。
  • BTreeMap<K, V>(有序映射)

    • 原理:基于B树实现的键值对集合,元素按键的顺序排序。
    • 特点:支持范围查询,插入和查询效率为O(log n),K需实现Ord trait。
  • HashSet<T>(哈希集合)

    • 原理:基于HashMap实现,仅存储键(值为单元类型()),确保元素唯一。
    • 特点:判断元素是否存在效率高(平均O(1)),T需实现HashEq trait。
  • BTreeSet<T>(有序集合)

    • 原理:基于BTreeMap实现,元素唯一且按顺序存储。
    • 特点:支持范围查询和排序,T需实现Ord trait。
  • LinkedList<T>(双向链表)

    • 原理:节点通过指针连接的双向链表,不连续存储。
    • 特点:插入和删除首尾元素效率高(O(1)),但随机访问效率低(O(n)),一般不推荐优先使用。

59. Vec<T>的基本操作(创建、添加、删除、访问元素),如何避免越界访问?

Vec<T>是Rust中最常用的动态数组类型,支持多种操作,同时需注意避免越界访问以保证安全性。

基本操作示例:
rust 复制代码
fn main() {
    // 1. 创建Vec
    let mut v1: Vec<i32> = Vec::new(); // 空Vec
    let v2 = vec![1, 2, 3]; // 使用vec!宏初始化
    let v3 = (0..5).collect::<Vec<i32>>(); // 从迭代器收集

    // 2. 添加元素(push/apppend)
    v1.push(4);
    v1.push(5);
    let mut v4 = vec![6, 7];
    v1.append(&mut v4); // 合并v4到v1(v4会被清空)
    println!("v1: {:?}", v1); // [4, 5, 6, 7]

    // 3. 访问元素
    let third = &v2[2]; // 索引访问(越界会panic)
    println!("v2[2]: {}", third); // 3

    let opt = v2.get(1); // get方法(返回Option<&T>,越界返回None)
    if let Some(val) = opt {
        println!("v2[1]: {}", val); // 2
    }

    // 4. 删除元素
    let last = v1.pop(); // 移除最后一个元素(返回Option<T>)
    println!("pop: {:?}", last); // Some(7)
    v1.remove(0); // 移除索引0的元素(返回被移除的值)
    println!("v1 after remove: {:?}", v1); // [5, 6]
}
避免越界访问的方法:
  1. 使用get方法 :返回Option<&T>,越界时返回None,可通过if letmatch安全处理。
  2. 检查长度 :通过v.len()判断索引是否在有效范围内(0 <= index < v.len())。
  3. 迭代器访问 :使用for item in &v遍历元素,无需手动管理索引。
  4. 模式匹配 :结合Vecis_empty方法和范围判断,避免访问空数组。

60. String&str的关系,String的内部结构是什么(提示:Vec<u8>)?

String&str的关系:
  • String :是一个可变的、拥有所有权的UTF-8字符串,数据存储在堆上,支持修改(如拼接、插入等)。
  • &str :是一个不可变的字符串切片&[u8]的特化),指向String或静态字符串(&'static str)中的一段连续UTF-8数据,不拥有所有权。
  • 关系:String可以通过&s(或s.as_str())转换为&str,而&str可以通过to_string()String::from()转换为String(会复制数据)。
String的内部结构:

String本质上是对Vec<u8>的封装,其内部结构包含三部分(与Vec一致):

  • 指针:指向堆上存储的UTF-8字节数据。
  • 长度 :当前字符串的字节数(len()方法返回)。
  • 容量 :堆上分配的总字节数(capacity()方法返回,大于等于长度)。

示例:

rust 复制代码
fn main() {
    let s = String::from("hello");
    // String -> &str
    let slice: &str = &s;
    println!("slice: {}", slice); // hello

    // &str -> String
    let s2 = slice.to_string();
    println!("s2: {}", s2); // hello

    // 查看String的内部字节(UTF-8编码)
    let bytes = s.as_bytes(); // &[u8]
    println!("bytes: {:?}", bytes); // [104, 101, 108, 108, 111](对应"hello"的ASCII码)
}

61. 如何处理UTF-8编码的字符串?为什么String不能直接通过索引访问字符?

处理UTF-8编码的字符串:

UTF-8是一种可变长度的Unicode编码(1-4字节表示一个字符),Rust的String&str均采用UTF-8编码。常见处理方式包括:

  1. 迭代字符 :使用chars()方法获取字符迭代器(每个元素是char类型)。
  2. 按字节处理 :使用as_bytes()获取字节切片(&[u8]),适合处理原始字节。
  3. 获取子串 :使用get()split_at()方法,需确保分割点在UTF-8字符边界上。

示例:

rust 复制代码
fn main() {
    let s = String::from("你好,world");

    // 迭代字符(char)
    for c in s.chars() {
        print!("{} ", c); // 你 好 , w o r l d 
    }
    println!();

    // 按字节处理(注意:中文字符占3字节)
    let bytes = s.as_bytes();
    println!("bytes: {:?}", bytes); // [228, 189, 160, 229, 165, 189, ...]

    // 安全获取子串(从索引0到6,对应"你好")
    if let Some(sub) = s.get(0..6) {
        println!("sub: {}", sub); // 你好
    }
}
为什么String不能直接通过索引访问字符?
  • UTF-8的可变长度特性 :一个char可能占1-4字节(如英文字母1字节,中文3字节),索引访问的是字节位置,而非字符位置,可能导致获取到不完整的字符(如半个中文字符)。
  • 安全性设计 :Rust为避免无效的UTF-8数据访问,禁止直接通过索引访问String的字符,强制使用chars()get()等安全方法。

62. HashMap<K, V>的使用场景,如何插入、查询、删除键值对?

HashMap<K, V>的使用场景:
  • 需通过唯一键快速查询值(如字典、缓存、用户ID与信息映射)。
  • 元素无序且对排序无要求。
  • 插入和查询操作频繁,且希望平均时间复杂度为O(1)。
基本操作示例:
rust 复制代码
use std::collections::HashMap;

fn main() {
    // 创建HashMap
    let mut map: HashMap<&str, i32> = HashMap::new();

    // 1. 插入键值对(insert返回旧值的Option)
    map.insert("one", 1);
    let old_val = map.insert("one", 100); // 覆盖旧值"one"
    println!("old_val: {:?}", old_val); // Some(1)

    // 2. 查询值(get返回Option<&V>)
    let val = map.get("one");
    if let Some(v) = val {
        println!("one: {}", v); // 100
    }

    // 3. 检查键是否存在
    if map.contains_key("two") {
        println!("two exists");
    } else {
        println!("two not exists"); // 执行此分支
    }

    // 4. 删除键值对(remove返回被删除值的Option)
    let removed = map.remove("one");
    println!("removed: {:?}", removed); // Some(100)
    println!("map after remove: {:?}", map); // {}
}
其他常用操作:
  • 遍历for (k, v) in &map 遍历键值对。
  • 更新值entry API(如map.entry("key").or_insert(0),不存在则插入默认值)。

63. BTreeMapHashMap的区别,何时选择BTreeMap

BTreeMapHashMap的区别:
特性 HashMap<K, V> BTreeMap<K, V>
内部实现 哈希表 B树(有序数据结构)
元素顺序 无序 按键的Ord trait排序
插入/查询复杂度 平均O(1),最坏O(n) O(log n)
键的约束 K: Hash + Eq K: Ord
范围查询支持 不支持 支持(如range(a..b)
内存占用 较高(哈希表需预留空间) 较低(B树结构紧凑)
何时选择BTreeMap
  1. 需要元素按键排序(如排行榜、字典序输出)。
  2. 需要范围查询(如查找键在[a, b]之间的所有元素)。
  3. 键类型实现Ord但不实现Hash(如自定义类型未实现Hash)。
  4. 对内存占用较敏感,且插入/查询频率适中(O(log n)可接受)。

示例(BTreeMap范围查询):

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

fn main() {
    let mut btree_map = BTreeMap::new();
    btree_map.insert(3, "three");
    btree_map.insert(1, "one");
    btree_map.insert(2, "two");

    // 自动按键排序
    println!("btree_map: {:?}", btree_map); // {1: "one", 2: "two", 3: "three"}

    // 范围查询(键1到2)
    let range = btree_map.range(1..=2);
    for (k, v) in range {
        println!("{}: {}", k, v); // 1: one, 2: two
    }
}

64. HashSetBTreeSet的特点,如何判断元素是否存在?

HashSet<T>的特点:
  • 基于HashMap<T, ()>实现,存储唯一元素(无键值对,仅值)。
  • 元素无序,T需实现Hash + Eq trait。
  • 插入、删除、判断存在的平均复杂度为O(1)。
  • 适合快速去重和存在性检查,对顺序无要求。
BTreeSet<T>的特点:
  • 基于BTreeMap<T, ()>实现,元素唯一且按Ord trait排序。
  • T需实现Ord trait。
  • 插入、删除、判断存在的复杂度为O(log n)。
  • 支持范围查询和有序遍历,适合需要排序或范围操作的场景。
判断元素是否存在的方法:

两种集合均通过contains方法判断元素是否存在,返回bool

示例:

rust 复制代码
use std::collections::{HashSet, BTreeSet};

fn main() {
    // HashSet
    let mut hash_set = HashSet::new();
    hash_set.insert("apple");
    hash_set.insert("banana");
    println!("HashSet has 'apple'? {}", hash_set.contains("apple")); // true

    // BTreeSet
    let mut btree_set = BTreeSet::new();
    btree_set.insert(3);
    btree_set.insert(1);
    btree_set.insert(2);
    println!("BTreeSet has 2? {}", btree_set.contains(&2)); // true
    println!("BTreeSet elements: {:?}", btree_set); // {1, 2, 3}(有序)
}

65. 什么是"切片(Slice)"?&[T]&str的关系是什么?

切片(Slice)的定义:

切片是一种不拥有所有权的引用类型 ,用于指向集合中一段连续的元素,格式为&[T](泛型切片)或&str(字符串切片)。它不存储数据,仅包含指针 (指向数据起始位置)和长度(元素数量),因此长度在编译时不确定,但访问时会检查边界以避免越界。

切片的特点:
  • 不可变切片(&[T]):不能修改指向的元素。
  • 可变切片(&mut [T]):可以修改指向的元素,但需遵守借用规则(同一时间只能有一个可变引用)。
  • 常用于安全访问集合的部分数据,无需复制整个集合。
&[T]&str的关系:
  • &str&[u8]特化版本,专门用于表示UTF-8编码的字符串切片,保证数据符合UTF-8标准。
  • &[T]是通用的切片类型,可指向任何连续存储的同类型元素(如Vec<T>、数组[T; N])。
  • 两者均为切片,结构相同(指针+长度),但&str有额外的UTF-8有效性约束。

示例:

rust 复制代码
fn main() {
    // 数组切片(&[T])
    let arr = [1, 2, 3, 4, 5];
    let slice: &[i32] = &arr[1..4]; // 指向arr的索引1到3(元素2,3,4)
    println!("array slice: {:?}", slice); // [2, 3, 4]

    // 字符串切片(&str)
    let s = String::from("hello world");
    let str_slice: &str = &s[0..5]; // 指向"hello"
    println!("string slice: {}", str_slice); // hello

    // &str本质是&[u8]的特化(但保证UTF-8)
    let bytes: &[u8] = str_slice.as_bytes();
    println!("bytes: {:?}", bytes); // [104, 101, 108, 108, 111]
}

二、120道Rust面试题目录列表

文章序号 Rust面试题120道
1 Rust面试题及详细答案120道(01-10)
2 Rust面试题及详细答案120道(11-18)
3 Rust面试题及详细答案120道(19-26)
4 Rust面试题及详细答案120道(27-32)
5 Rust面试题及详细答案120道(33-41)
6 Rust面试题及详细答案120道(42-50)
7 Rust面试题及详细答案120道(51-57)
8 Rust面试题及详细答案120道(58-65)
9 Rust面试题及详细答案120道(66-71)
10 Rust面试题及详细答案120道(72-80)
11 Rust面试题及详细答案120道(81-89)
12 Rust面试题及详细答案120道(90-98)
13 Rust面试题及详细答案120道(99-105)
14 Rust面试题及详细答案120道(106-114)
15 Rust面试题及详细答案120道(115-120)
相关推荐
还是大剑师兰特10 小时前
.prettierrc有什么作用,怎么书写
大剑师·prettierrc教程
还是大剑师兰特9 天前
Scala面试题及详细答案100道(11-20)-- 函数式编程基础
scala·大剑师·scala面试题
还是大剑师兰特9 天前
Spring面试题及详细答案 125道(1-15) -- 核心概念与基础1
spring·大剑师·spring面试题·spring教程
还是大剑师兰特9 天前
Flink面试题及详细答案100道(1-20)- 基础概念与架构
大数据·flink·大剑师·flink面试题
还是大剑师兰特10 天前
Rust面试题及详细答案120道(51-57)-- 错误处理
大剑师·rust面试题·rust教程
还是大剑师兰特12 天前
Node.js面试题及详细答案120题(16-30) -- 核心模块篇
node.js·大剑师·nodejs面试题
还是大剑师兰特12 天前
浏览器面试题及详细答案 88道(23-33)
大剑师·浏览器面试题
还是大剑师兰特12 天前
C#面试题及详细答案120道(11-20)-- 面向对象编程(OOP)
大剑师·c#面试题
还是大剑师兰特13 天前
浏览器面试题及详细答案 88道(12-22)
大剑师·浏览器面试题