在Rust开发中,Vec 和 HashMap 是 标准库 提供的两大核心动态集合类型,基于堆内存分配实现长度动态调整,覆盖了绝大多数日常数据存储场景。
Vec作为动态连续数组,主打有序索引访问;
HashMap作为哈希表,主打键值对快速查找,
二者均严格遵循Rust的所有权规则,兼顾内存安全与性能。本文将从基础定义到进阶实战,全方位拆解两者的用法,结合详细示例代码与拓展知识点,帮你彻底掌握这两个高频工具。
一、Vec:Rust的动态连续数组
Vec全称为Vec<T>,位于std::vec模块,是存储同类型元素的动态连续容器,可看作原生数组的"升级版"------原生数组长度固定且分配在栈上,而Vec长度可在运行时增减,数据存储在堆上,仅通过栈上的指针、长度、容量三个字段管理堆内存。
1.1 核心特性与底层结构
-
连续内存:元素在堆上连续排布,支持O(1)时间复杂度的索引访问,缓存友好性强。
-
动态扩容:当元素数量超过容量(cap)时,自动申请2倍(小容量时)或1.5倍(大容量时)的新堆内存,拷贝原有元素后释放旧内存,扩容有性能开销。
-
所有权管理:Vec拥有所有元素的所有权,析构时自动释放堆内存,无内存泄漏风险。
-
安全访问 :提供
get/get_mut等安全方法,避免索引越界导致的运行时恐慌。
底层三大字段(64位系统占24字节):
-
ptr:指向堆内存中元素起始地址的指针;
-
len:当前已存储的元素数量;
-
cap:当前堆内存可容纳的最大元素数量(cap ≥ len)。
1.2 定义与初始化
Vec支持多种初始化方式,适配不同开发场景,编译器可通过上下文自动推导元素类型,也可显式指定。
1.2.1 空Vec初始化
适用于提前声明容器,后续动态添加元素的场景:
rust
fn main() {
// 方式1:显式指定类型
let mut v1: Vec<i32> = Vec::new();
// 方式2:turbofish语法(推荐,更简洁)
let mut v2 = Vec::<String>::new();
// 方式3:从空数组转换(较少用)
let mut v3: Vec<f64> = [].to_vec();
v1.push(10);
v2.push(String::from("Rust"));
println!("v1={:?}, v2={:?}, v3={:?}", v1, v2, v3);
// 输出:v1=[10], v2=["Rust"], v3=[]
}
1.2.2 字面量初始化(vec!宏)
Rust内置vec!宏,语法与数组一致,是最常用的初始化方式,支持直接传入初始元素或重复元素:
rust
fn main() {
// 基础字面量初始化,自动推导类型为Vec<i32>
let v1 = vec![1, 2, 3, 4];
// 重复元素初始化:元素x重复n次(x需实现Copy trait)
let v2 = vec![0; 5]; // 等价于[0,0,0,0,0]
// 显式指定元素类型(避免推导歧义)
let v3 = vec![1u8, 2u8, 3u8];
println!("v1={:?}, v2={:?}, v3={:?}", v1, v2, v3);
// 输出:v1=[1,2,3,4], v2=[0,0,0,0,0], v3=[1,2,3]
}
注意 :vec![x; n]仅适用于实现Copy trait的类型(如数值、bool),非Copy类型(如String)需用迭代器初始化。
1.2.3 迭代器初始化
通过迭代器的collect方法生成Vec,灵活度最高,可从数组、范围、其他集合转换而来:
rust
fn main() {
// 从数组迭代器生成
let arr = [1, 2, 3];
let v1: Vec<i32> = arr.iter().cloned().collect();
// 从范围迭代器生成
let v2: Vec<u32> = (1..=5).collect();
// 迭代器映射后生成(修改元素)
let v3: Vec<i32> = v1.iter().map(|&x| x * 2).collect();
// 过滤后生成
let v4: Vec<i32> = v3.iter().filter(|&&x| x > 5).collect();
println!("v1={:?}, v2={:?}, v3={:?}, v4={:?}", v1, v2, v3, v4);
// 输出:v1=[1,2,3], v2=[1,2,3,4,5], v3=[2,4,6], v4=[6]
}
collect是泛型方法,需显式指定Vec类型(或通过上下文推导),否则编译器无法确定目标容器。
1.2.4 预分配容量初始化
通过Vec::with_capacity(n)预分配n个元素的堆内存(len=0,cap=n),适用于已知元素数量上限的场景,可避免多次扩容:
rust
fn main() {
let mut v = Vec::with_capacity(100);
println!("初始化:len={}, cap={}", v.len(), v.capacity()); // len=0, cap=100
// 插入10个元素,未超容量,无需扩容
for i in 0..10 {
v.push(i);
}
println!("插入10个元素:len={}, cap={}", v.len(), v.capacity()); // len=10, cap=100
// 插入91个元素,触发扩容
for i in 10..101 {
v.push(i);
}
println!("插入101个元素:len={}, cap={}", v.len(), v.capacity()); // len=101, cap=200
}
1.3 核心操作
Vec的操作围绕元素增删改查、容量管理、迭代展开,所有修改操作需将Vec声明为mut(可变)。
1.3.1 元素添加
-
push:末尾添加单个元素,O(1)(未扩容)/ O(n)(扩容);
-
extend:末尾批量添加迭代器元素,比多次push高效;
-
insert:指定索引插入元素,后续元素后移,O(n)(不推荐频繁使用)。
rust
fn main() {
let mut v = vec![1, 2, 3];
v.push(4);
println!("push后:{:?}", v); // [1,2,3,4]
v.extend([5, 6, 7].iter().cloned());
v.extend(8..=10);
println!("extend后:{:?}", v); // [1,2,3,4,5,6,7,8,9,10]
v.insert(0, 0); // 索引0插入0
v.insert(5, 100); // 索引5插入100
println!("insert后:{:?}", v); // [0,1,2,3,4,100,5,6,7,8,9,10]
}
1.3.2 元素删除
-
pop :删除并返回末尾元素,O(1),返回
Option<T>(空Vec返回None); -
remove:删除指定索引元素,后续元素前移,O(n);
-
clear:清空所有元素,len=0,cap不变(不释放内存,可复用);
-
drain:删除指定范围元素,返回迭代器(可遍历被删除元素)。
rust
fn main() {
let mut v = vec![0, 1, 2, 3, 4, 5];
let last = v.pop();
println!("pop后:{:?}, 被删元素:{:?}", v, last); // [0,1,2,3,4], Some(5)
let idx_2 = v.remove(2);
println!("remove后:{:?}, 被删元素:{}", v, idx_2); // [0,1,3,4], 2
v.clear();
println!("clear后:len={}, cap={}", v.len(), v.capacity()); // len=0, cap=6
v.extend(0..3);
let deleted: Vec<i32> = v.drain(1..).collect();
println!("drain后:{:?}, 被删元素:{:?}", v, deleted); // [0], [1,2]
}
1.3.3 元素访问
提供安全与非安全两种访问方式,优先使用安全方式避免恐慌:
rust
fn main() {
let mut v = vec![10, 20, 30, 40];
// 非安全索引访问(越界恐慌)
let first = v[0];
let mut third = &mut v[2];
*third = 300;
// 安全访问(返回Option)
let second = v.get(1);
let mut fourth = v.get_mut(3);
if let Some(val) = fourth {
*val = 400;
}
// 越界对比
// let err = v[10]; // 恐慌
let err_safe = v.get(10); // None
println!("v={:?}, second={:?}, err_safe={:?}", v, second, err_safe);
// 输出:v=[10,20,300,400], second=Some(20), err_safe=None
}
1.3.4 迭代遍历
Vec实现了IntoIterator trait,支持三种迭代方式,比索引遍历更安全高效:
rust
fn main() {
let v = vec![1, 2, 3, 4];
let mut v_mut = vec![10, 20, 30, 40];
// 不可变迭代(仅读取,不转移所有权)
println!("不可变迭代:");
for num in &v {
print!("{} ", num);
}
// 可变迭代(修改元素)
println!("\n可变迭代(修改后):");
for num in &mut v_mut {
*num *= 2;
print!("{} ", num);
}
// 消费型迭代(转移所有权,迭代后Vec失效)
let sum: i32 = v.into_iter().sum();
println!("\n元素和:{}", sum);
}
1.4 进阶拓展与常见坑
1.4.1 与切片的零成本转换
Vec可零成本转换为切片&[T]/&mut [T](无内存拷贝),切片是Vec的视图,可作为函数参数兼容Vec、数组:
rust
fn main() {
let mut v = vec![1, 2, 3, 4];
let slice: &[i32] = &v;
let mut_slice: &mut [i32] = &mut v;
print_slice(slice);
modify_slice(mut_slice, 10);
println!("修改后Vec:{:?}", v); // [10,20,30,40]
}
fn print_slice(s: &[i32]) {
println!("切片元素:{:?}", s);
}
fn modify_slice(s: &mut [i32], n: i32) {
for num in s {
*num *= n;
}
}
1.4.2 嵌套Vec(多维动态数组)
Vec支持嵌套,实现多维动态数组,每一层长度可独立调整:
rust
fn main() {
// 初始化3行4列的二维Vec
let mut matrix = Vec::with_capacity(3);
for i in 0..3 {
let mut row = Vec::with_capacity(4);
for j in 0..4 {
row.push(i * 4 + j);
}
matrix.push(row);
}
println!("二维Vec:{:?}", matrix); // [[0,1,2,3], [4,5,6,7], [8,9,10,11]]
// 安全访问元素
let val = matrix.get(1).and_then(|row| row.get(2));
println!("matrix[1][2] = {:?}", val); // Some(6)
}
1.4.3 常见坑与避坑要点
-
索引越界恐慌 :优先用
get/get_mut,通过if let处理Option。 -
扩容导致引用失效:Rust借用检查器会阻止"持有引用时修改Vec",从根源避免悬垂引用。
-
消费型迭代后复用Vec :消费迭代(
into_iter)后Vec所有权转移,需保留则用iter/iter_mut。 -
忽略预分配容量 :批量添加元素前用
with_capacity/reserve,减少扩容开销。
二、HashMap:Rust的哈希表键值对集合
HashMap全称为HashMap<K, V>,位于std::collections模块,是基于哈希算法的键值对容器,键唯一,支持O(1)平均时间复杂度的插入、删除、查找,适用于通过键快速定位值的场景。
2.1 核心特性与底层结构
-
键唯一性:同一键仅对应一个值,插入重复键会覆盖旧值。
-
无序存储 :遍历顺序与插入顺序无关(需有序可改用
LinkedHashMap)。 -
键约束 :键类型
K需实现Hash(支持哈希计算)和Eq(支持相等性判断),内置类型(i32、String等)均已实现。 -
哈希冲突处理 :采用拉链法,底层由桶数组和链表/红黑树组成,使用
SipHash算法抗碰撞。
2.2 定义与初始化
使用前需显式导入use std::collections::HashMap;,支持空初始化、迭代器初始化、预分配容量初始化。
2.2.1 空HashMap初始化
rust
use std::collections::HashMap;
fn main() {
// 显式指定类型
let mut map1: HashMap<i32, &str> = HashMap::new();
// turbofish语法
let mut map2 = HashMap::<String, i32>::new();
map1.insert(1, "one");
map2.insert(String::from("two"), 2);
println!("map1={:?}, map2={:?}", map1, map2);
// 输出:map1={1: "one"}, map2={"two": 2}
}
2.2.2 迭代器初始化
通过数组+collect模拟字面量初始化,或从两个迭代器拼接键值对:
rust
use std::collections::HashMap;
fn main() {
// 数组+collect(推荐)
let map1: HashMap<i32, &str> = [(1, "one"), (2, "two")].iter().cloned().collect();
// 两个迭代器zip拼接
let keys = vec![1, 2, 3];
let values = vec!["one", "two", "three"];
let map2: HashMap<_, _> = keys.into_iter().zip(values.into_iter()).collect();
println!("map1={:?}, map2={:?}", map1, map2);
// 输出:map1={1: "one", 2: "two"}, map2={1: "one", 2: "two", 3: "three"}
}
2.2.3 预分配容量初始化
HashMap::with_capacity(n)预分配桶数组,减少扩容开销(HashMap扩容需重新计算哈希,开销比Vec大):
rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::with_capacity(100);
println!("初始化:len={}, cap={}", map.len(), map.capacity()); // len=0, cap=100
for i in 0..10 {
map.insert(i, i.to_string());
}
println!("插入10个:len={}, cap={}", map.len(), map.capacity()); // len=10, cap=100
}
2.3 核心操作
2.3.1 键值对插入
-
insert :插入键值对,覆盖重复键,返回
Option<V>(旧值); -
extend:批量插入键值对迭代器。
rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
let old1 = map.insert(1, "one"); // None
let old2 = map.insert(1, "one_new"); // Some("one")
println!("map={:?}, old1={:?}, old2={:?}", map, old1, old2);
// 输出:map={1: "one_new"}, old1=None, old2=Some("one")
map.extend([(2, "two"), (3, "three")].iter().cloned());
println!("批量插入后:{:?}", map); // {1: "one_new", 2: "two", 3: "three"}
}
2.3.2 键值对查询
-
contains_key :判断键是否存在,返回
bool; -
get /
get_mut:获取值的引用,返回Option; -
get_key_value :获取键值对引用,返回
Option<(&K, &V)>。
rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::from([(1, "one"), (2, "two")]);
println!("是否存在键2:{}", map.contains_key(&2)); // true
println!("键1的值:{:?}", map.get(&1)); // Some("one")
if let Some(v) = map.get_mut(&2) {
*v = "two_new";
}
println!("修改后键2的值:{:?}", map.get(&2)); // Some("two_new")
}
2.3.3 进阶:Entry API(高效条件操作)
Entry API是HashMap的核心优势,通过map.entry(key)获取Entry枚举,实现"键存在则修改,不存在则插入",仅需一次哈希计算:
rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::from([(1, 10), (2, 20)]);
// 键存在则修改,不存在则插入
let val1 = map.entry(1).or_insert(0);
*val1 += 5; // 10→15
map.entry(3).or_insert(30); // 插入3:30
// 键不存在则通过闭包生成值(延迟初始化)
map.entry(4).or_insert_with(|| 4 * 10); // 4:40
// 键存在则修改,不存在则插入默认值(V需实现Default)
map.entry(5).or_default(); // 5:0
// 链式调用:修改+插入
map.entry(2).and_modify(|v| *v *= 2).or_insert(0); // 20→40
println!("最终map:{:?}", map);
// 输出:{1:15, 2:40, 3:30, 4:40, 5:0}
}
2.3.4 键值对删除与遍历
rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::from([(1, "one"), (2, "two"), (3, "three")]);
// 删除键1,返回值
let del = map.remove(&1);
println!("删除后:{:?}, 被删元素:{:?}", map, del); // {2:"two",3:"three"}, Some("one")
// 遍历键
println!("键:");
for k in map.keys() {
print!("{} ", k);
}
// 遍历键值对并修改值
let mut map = HashMap::from([(1, String::from("one"))]);
for (_, v) in map.iter_mut() {
v.push_str("_new");
}
println!("\n修改后:{:?}", map); // {1: "one_new"}
}
2.4 进阶拓展与常见坑
2.4.1 自定义键类型
自定义类型作为键,需手动实现Hash、Eq、PartialEq trait,可通过#[derive]自动推导:
rust
use std::collections::HashMap;
use std::hash::Hash;
#[derive(Debug, Hash, Eq, PartialEq)]
struct User {
id: i32,
name: String,
}
fn main() {
let mut map = HashMap::new();
let user = User { id: 1, name: String::from("Alice") };
map.insert(user, 90); // 以User为键,分数为值
let user2 = User { id: 1, name: String::from("Alice") };
println!("分数:{:?}", map.get(&user2)); // Some(90)
}
2.4.2 常见坑与避坑要点
-
键的所有权问题:插入非Copy类型键(如String)后,所有权转移给HashMap,外部无法再使用。
-
哈希冲突影响性能 :标准库
SipHash算法平衡安全与性能,无需手动处理冲突,极端场景可优化哈希函数。 -
依赖遍历顺序 :HashMap无序,需有序可改用
LinkedHashMap(保留插入顺序)或BTreeMap(按键排序)。 -
过度预分配容量:预分配过多会浪费内存,根据实际数量合理设置。
三、Vec与HashMap对比与选型建议
| 特性 | Vec | HashMap |
|---|---|---|
| 存储结构 | 连续堆内存,同类型元素 | 哈希表,键值对集合 |
| 访问方式 | 索引访问(O(1)) | 键访问(O(1)平均) |
| 有序性 | 插入顺序即遍历顺序 | 无序 |
| 核心场景 | 有序列表、批量处理、索引定位 | 键值映射、快速查找/删除 |
| 扩容开销 | 拷贝元素,开销中等 | 重新计算哈希+拷贝,开销较高 |
| 选型建议: |
-
需按顺序访问、频繁通过索引操作 → 选Vec;
-
需通过唯一键快速查找、插入删除 → 选HashMap;
-
需有序键值对 → 选
BTreeMap;需保留插入顺序 → 选LinkedHashMap。
四、实战案例:结合Vec与HashMap实现学生成绩管理
rust
use std::collections::HashMap;
use std::io;
// 学生结构体
#[derive(Debug)]
struct Student {
name: String,
scores: Vec<i32>, // 多科成绩
}
fn main() {
let mut student_map: HashMap<i32, Student> = HashMap::new();
// 添加学生
add_student(&mut student_map, 1, "Alice", vec![85, 92, 78]);
add_student(&mut student_map, 2, "Bob", vec![90, 88, 95]);
// 查询学生成绩
query_student(&student_map, 1);
// 计算平均分
calculate_average(&student_map, 2);
// 删除学生
delete_student(&mut student_map, 1);
println!("删除后学生列表:{:?}", student_map);
}
// 添加学生
fn add_student(map: &mut HashMap<i32, Student>, id: i32, name: &str, scores: Vec<i32>) {
map.insert(id, Student {
name: name.to_string(),
scores,
});
}
// 查询学生成绩
fn query_student(map: &HashMap<i32, Student>, id: i32) {
if let Some(student) = map.get(&id) {
println!("学生{}的成绩:{:?}", student.name, student.scores);
} else {
println!("未找到ID为{}的学生", id);
}
}
// 计算平均分
fn calculate_average(map: &HashMap<i32, Student>, id: i32) {
if let Some(student) = map.get(&id) {
let total: i32 = student.scores.iter().sum();
let avg = total as f64 / student.scores.len() as f64;
println!("学生{}的平均分:{:.1}", student.name, avg);
}
}
// 删除学生
fn delete_student(map: &mut HashMap<i32, Student>, id: i32) {
map.remove(&id);
println!("已删除ID为{}的学生", id);
}
五、总结
Vec和HashMap是Rust开发中不可或缺的集合工具,Vec凭借连续内存和索引优势,适合有序场景;HashMap依靠哈希算法,主打键值对快速操作。两者均严格遵循所有权规则,确保内存安全。掌握它们的初始化、核心操作、进阶技巧与避坑要点,结合实际场景合理选型,能大幅提升代码效率与健壮性。实际开发中,两者常结合使用,构建更复杂的结构化数据存储方案。