Rust面试题及详细答案120道(58-65)-- 集合类型
《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux… 。
文章目录
- 一、本文面试题目录
- 58. 简述Rust标准库中的主要集合类型(`Vec`、`String`、`HashMap`等)
- 59. `Vec<T>`的基本操作(创建、添加、删除、访问元素),如何避免越界访问?
- 基本操作示例:
- 避免越界访问的方法:
- 60. `String`与`&str`的关系,`String`的内部结构是什么(提示:`Vec<u8>`)?
- `String`与`&str`的关系:
- `String`的内部结构:
- 61. 如何处理UTF-8编码的字符串?为什么`String`不能直接通过索引访问字符?
- 处理UTF-8编码的字符串:
- 为什么`String`不能直接通过索引访问字符?
- 62. `HashMap<K, V>`的使用场景,如何插入、查询、删除键值对?
- `HashMap<K, V>`的使用场景:
- 基本操作示例:
- 其他常用操作:
- 63. `BTreeMap`与`HashMap`的区别,何时选择`BTreeMap`?
- `BTreeMap`与`HashMap`的区别:
- 何时选择`BTreeMap`?
- 64. `HashSet`和`BTreeSet`的特点,如何判断元素是否存在?
- `HashSet<T>`的特点:
- `BTreeSet<T>`的特点:
- 判断元素是否存在的方法:
- 65. 什么是“切片(Slice)”?`&[T]`和`&str`的关系是什么?
- 切片(Slice)的定义:
- 切片的特点:
- `&[T]`和`&str`的关系:
- 二、120道Rust面试题目录列表
一、本文面试题目录
58. 简述Rust标准库中的主要集合类型(Vec
、String
、HashMap
等)
Rust标准库提供了多种集合类型,用于存储和管理数据集合,它们都在堆上分配内存,且具有不同的特性和适用场景。主要集合类型包括:
-
Vec<T>
(动态数组):- 原理:连续存储相同类型
T
的元素,支持动态扩容,内部基于Vec<u8>
实现。 - 特点:随机访问效率高(O(1)),适合存储有序、可重复的元素。
- 原理:连续存储相同类型
-
String
(字符串):- 原理:UTF-8编码的可变字符串,内部本质是
Vec<u8>
,但保证数据符合UTF-8标准。 - 特点:支持字符串拼接、修改,适合处理文本数据。
- 原理:UTF-8编码的可变字符串,内部本质是
-
HashMap<K, V>
(哈希表):- 原理:基于哈希函数存储键值对(
K
为键,V
为值),通过键快速查找值。 - 特点:插入和查询效率高(平均O(1)),但元素无序,
K
需实现Hash
和Eq
trait。
- 原理:基于哈希函数存储键值对(
-
BTreeMap<K, V>
(有序映射):- 原理:基于B树实现的键值对集合,元素按键的顺序排序。
- 特点:支持范围查询,插入和查询效率为O(log n),
K
需实现Ord
trait。
-
HashSet<T>
(哈希集合):- 原理:基于
HashMap
实现,仅存储键(值为单元类型()
),确保元素唯一。 - 特点:判断元素是否存在效率高(平均O(1)),
T
需实现Hash
和Eq
trait。
- 原理:基于
-
BTreeSet<T>
(有序集合):- 原理:基于
BTreeMap
实现,元素唯一且按顺序存储。 - 特点:支持范围查询和排序,
T
需实现Ord
trait。
- 原理:基于
-
LinkedList<T>
(双向链表):- 原理:节点通过指针连接的双向链表,不连续存储。
- 特点:插入和删除首尾元素效率高(O(1)),但随机访问效率低(O(n)),一般不推荐优先使用。
59. Vec<T>
的基本操作(创建、添加、删除、访问元素),如何避免越界访问?
Vec<T>
是Rust中最常用的动态数组类型,支持多种操作,同时需注意避免越界访问以保证安全性。
基本操作示例:
fn main() {// 1. 创建Veclet mut v1: Vec<i32> = Vec::new(); // 空Veclet 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); // 3let 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]
}
避免越界访问的方法:
- 使用
get
方法:返回Option<&T>
,越界时返回None
,可通过if let
或match
安全处理。 - 检查长度:通过
v.len()
判断索引是否在有效范围内(0 <= index < v.len()
)。 - 迭代器访问:使用
for item in &v
遍历元素,无需手动管理索引。 - 模式匹配:结合
Vec
的is_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()
方法返回,大于等于长度)。
示例:
fn main() {let s = String::from("hello");// String -> &strlet slice: &str = &s;println!("slice: {}", slice); // hello// &str -> Stringlet 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编码。常见处理方式包括:
- 迭代字符:使用
chars()
方法获取字符迭代器(每个元素是char
类型)。 - 按字节处理:使用
as_bytes()
获取字节切片(&[u8]
),适合处理原始字节。 - 获取子串:使用
get()
或split_at()
方法,需确保分割点在UTF-8字符边界上。
示例:
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)。
基本操作示例:
use std::collections::HashMap;fn main() {// 创建HashMaplet 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. BTreeMap
与HashMap
的区别,何时选择BTreeMap
?
BTreeMap
与HashMap
的区别:
特性 | 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
?
- 需要元素按键排序(如排行榜、字典序输出)。
- 需要范围查询(如查找键在
[a, b]
之间的所有元素)。 - 键类型实现
Ord
但不实现Hash
(如自定义类型未实现Hash
)。 - 对内存占用较敏感,且插入/查询频率适中(
O(log n)
可接受)。
示例(BTreeMap
范围查询):
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. HashSet
和BTreeSet
的特点,如何判断元素是否存在?
HashSet<T>
的特点:
- 基于
HashMap<T, ()>
实现,存储唯一元素(无键值对,仅值)。 - 元素无序,
T
需实现Hash + Eq
trait。 - 插入、删除、判断存在的平均复杂度为O(1)。
- 适合快速去重和存在性检查,对顺序无要求。
BTreeSet<T>
的特点:
- 基于
BTreeMap<T, ()>
实现,元素唯一且按Ord
trait排序。 T
需实现Ord
trait。- 插入、删除、判断存在的复杂度为O(log n)。
- 支持范围查询和有序遍历,适合需要排序或范围操作的场景。
判断元素是否存在的方法:
两种集合均通过contains
方法判断元素是否存在,返回bool
。
示例:
use std::collections::{HashSet, BTreeSet};fn main() {// HashSetlet mut hash_set = HashSet::new();hash_set.insert("apple");hash_set.insert("banana");println!("HashSet has 'apple'? {}", hash_set.contains("apple")); // true// BTreeSetlet 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)); // trueprintln!("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有效性约束。
示例:
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) |