Naive!
Java 的 ArrayList
、String
、HashMap
在 GC 的保护下随便用。Rust 的 Vec
、String
、HashMap
每一次操作都要过所有权系统的审查。
这不是负担,这是进化。
Java 集合:GC 庇护下的放纵
Java 的"舒适圈"
// Java 的任性代码
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
String first = list.get(0); // 随便取
String copy = new String(first); // 随便复制
list.clear(); // 随便清空
// GC 负责一切,你什么都不用担心
爽吗?当然爽。高效吗?呵呵。
隐藏的成本
Java 集合的每一次操作都有隐性成本:
- 装箱拆箱 -
Integer
vsint
的开销 - GC 压力 - 频繁分配回收的暂停
- 内存碎片 - 对象散布在堆上各处
- 缓存不友好 - 引用跳跃,缓存命中率低
舒适是有代价的。
Vec:ArrayList 的完美进化
创建和基本操作
// 创建 Vec
let mut vec = Vec::new(); // 空向量
let mut vec = vec![1, 2, 3]; // 宏创建
// 添加元素
vec.push(4);
vec.push(5);
// 访问元素
let first = &vec[0]; // 借用,不转移所有权
let first = vec.get(0); // 返回 Option<&T>,更安全
// 遍历
for item in &vec { // 借用遍历
println!("{}", item);
}
for item in vec { // 消费遍历,vec 失效
println!("{}", item);
}
看到区别了吗?每个操作都要考虑所有权。
所有权的深层影响
fn process_vec() {
let mut vec = vec![1, 2, 3];
let first = vec[0]; // Copy trait,直接复制
println!("First: {}", first);
vec.push(4); // vec 依然有效,因为 i32 实现了 Copy
// 如果是 String 呢?
let mut string_vec = vec!["hello".to_string(), "world".to_string()];
// let first_string = string_vec[0]; // 编译错误!String 没有 Copy
let first_string = &string_vec[0]; // 只能借用
println!("First string: {}", first_string);
}
这种限制看似麻烦,实际上保证了内存安全。
性能优势:连续内存布局
// Rust Vec:连续内存,缓存友好
let vec: Vec<i32> = (0..1000000).collect();
// Java ArrayList:对象引用数组,缓存不友好
// List<Integer> list = IntStream.range(0, 1000000)
// .boxed().collect(Collectors.toList());
Vec 的元素连续存储,ArrayList 存储的是引用。 谁的性能更好?显而易见。
String:不再是"不可变"的字符串
Java String:不可变的假象
String s = "hello";
s += " world"; // 创建新对象,原对象变垃圾
// 每次"修改"都创建新对象,GC 压力巨大
Rust String:真正的可变字符串
let mut s = String::from("hello");
s.push_str(" world"); // 原地修改,无额外分配
// 高效的字符串构建
let s = format!("{} {}", "hello", "world"); // 一次分配
// 字符串拼接
let mut result = String::new();
result.push_str("hello");
result.push(' ');
result.push_str("world");
真正的可变性,真正的性能。
String vs &str:所有权的经典案例
fn string_processing() {
let owned = String::from("hello world"); // 拥有数据
let borrowed = "hello world"; // 借用静态数据
// 函数签名的选择
process_owned(owned); // 转移所有权
// process_owned(owned); // 编译错误!owned 已失效
process_borrowed(borrowed); // 借用
process_borrowed(borrowed); // 可以重复借用
}
fn process_owned(s: String) { // 获得所有权
println!("{}", s);
}
fn process_borrowed(s: &str) { // 只是借用
println!("{}", s);
}
API 设计的哲学:能借用就不要夺取所有权。
HashMap<K,V>:哈希表的所有权挑战
基本操作
use std::collections::HashMap;
let mut map = HashMap::new();
// 插入键值对
map.insert("key1".to_string(), "value1".to_string());
map.insert("key2".to_string(), "value2".to_string());
// 访问值
let value = map.get("key1"); // 返回 Option<&V>
match value {
Some(v) => println!("Found: {}", v),
None => println!("Not found"),
}
// 遍历
for (key, value) in &map { // 借用遍历
println!("{}: {}", key, value);
}
所有权的复杂性
fn hashmap_ownership() {
let mut map = HashMap::new();
let key = String::from("important");
let value = String::from("data");
map.insert(key, value); // key 和 value 的所有权被转移
// println!("{}", key); // 编译错误!key 已被移动
// 解决方案1:克隆
let key = String::from("important");
let value = String::from("data");
map.insert(key.clone(), value.clone()); // 克隆后插入
println!("{}", key); // key 依然有效
// 解决方案2:使用引用作为键(生命周期复杂)
// 解决方案3:使用 &str 作为键(适合静态字符串)
}
HashMap 的每次插入都可能转移所有权。 这强制你思考数据的生命周期。
高级模式:entry API
// Java 的丑陋模式
// if (!map.containsKey(key)) {
// map.put(key, new ArrayList<>());
// }
// map.get(key).add(value);
// Rust 的优雅模式
map.entry(key)
.or_insert_with(Vec::new)
.push(value);
// 或者更简洁
map.entry(key).or_default().push(value);
entry API 避免了重复查找,性能更好。
内存布局:连续 vs 分散
Java 集合的内存布局
ArrayList<String>:
┌─────┐ ┌────────┐
│ ref ├───►│ array │
└─────┘ │ [ref1] ├─┐
│ [ref2] ├─┼─┐
│ [ref3] ├─┼─┼─┐
└────────┘ │ │ │
│ │ └─► "world"
│ └───► "hello"
└─────► "foo"
引用跳跃,缓存不友好。
Rust 集合的内存布局
Vec<String>:
┌─────┐ ┌─────────────────┐
│Vec ├───►│ String1 │
└─────┘ │ String2 │
│ String3 │
└─────────────────┘
连续存储,缓存友好,性能更佳。
实战技巧:避免不必要的克隆
反模式:过度克隆
// 糟糕:不必要的克隆
fn bad_example(vec: Vec<String>) -> Vec<String> {
let mut result = Vec::new();
for item in vec {
result.push(item.clone()); // 不必要的克隆
}
result
}
良好模式:智能借用
// 优秀:避免不必要的所有权转移
fn good_example(vec: &[String]) -> Vec<&str> {
vec.iter().map(|s| s.as_str()).collect()
}
// 或者使用迭代器
fn iterator_example(vec: Vec<String>) -> Vec<String> {
vec.into_iter() // 消费迭代器,转移所有权
.filter(|s| !s.is_empty())
.collect()
}
理解何时需要所有权,何时只需要借用。
写在最后:集合的哲学转变
Java 集合的哲学:给你便利,GC 负责后果。
Rust 集合的哲学:给你控制,你负责思考。
这种转变带来的收益:
- 内存效率 - 无 GC 开销,无装箱成本
- 性能可预测 - 无突然的 GC 暂停
- 缓存友好 - 连续内存布局
- 线程安全 - 所有权保证无数据竞争
当你习惯了 Rust 的集合操作,你会发现:
- 代码更安全了(编译期保证)
- 性能更可控了(无 GC 惊喜)
- 内存使用更高效了(精确控制)
- 并发编程更简单了(所有权保证安全)
这就是现代系统编程语言的集合应该有的样子。
下一章我们要学习 Rust 的错误处理进阶技巧。准备好看看如何构建真正健壮的应用程序了吗?