概述
- 参考文档:std
String、Vec、HashMap等常用数据结构都在标准库std里,不需要第三方依赖- 语法基础见Rust 基础语法,涉及所有权的地方见Rust 所有权与借用
- 常见需求对应的容器:
flowchart TD
Q{"要存什么"} -->|"一列同构元素,按下标访问"| V["Vec<T>"]
Q -->|"两端进出"| D["VecDeque<T>"]
Q -->|"键值映射"| H{"需要按键有序?"}
H -->|"否"| M["HashMap<K, V>"]
H -->|"是"| B["BTreeMap<K, V>"]
Q -->|"去重或成员判断"| S["HashSet<T>"]
Q -->|"文本"| T["String"]String
String持有堆上可增长的UTF-8数据,&str是对字符串数据的借用视图,切片与所有权背景见Rust 基础语法创建:
String::from与.to_string()等价,String::new()创建空串后续再追加rustlet a = String::from("hello"); let b = "hello".to_string(); let c = String::new();追加与插入:
push追加单个char,push_str追加&str,insert按字节下标插入rustlet mut s = String::from("he"); s.push('l'); // 追加字符 s.push_str("lo"); // 追加 &str s.insert(0, 'H'); // 下标按字节计,多字节字符前插入要小心拼接:
+会移走左侧的String,多个部分拼接用format!,它不取走任何参数rustlet s1 = String::from("ab"); let s2 = String::from("cd"); let s3 = s1 + &s2; // s1 被移走,此后不可用 let s4 = format!("{}{}", s2, s3); // s2 与 s3 都还能用不支持
s[0]这样的下标访问:字符串按UTF-8逐字节存储,字节下标可能落在字符中间,编译器直接拒绝按字符遍历用
chars(),按字节遍历用bytes(),字节数用len()rustlet s = "你好"; s.len(); // 6:两个汉字共 6 个 UTF-8 字节 s.chars().count(); // 2:字符数chars()数的是Unicode标量值,带音调符号或组合字符的字素会被数成多个
与
&str互转:&string在需要&str的位置自动转换,反向用.to_string()遍历与查找:
chars()产出char,find返回第一个匹配的位置rustfor c in "rust".chars() { } if let Some(i) = "hello".find('l') { } // i 是字节下标 2
Vec
动态数组:元素连续存放在堆上,长度可变,日常最常用的序列容器
创建:
vec!宏带初值,Vec::new()创建空的rustlet v = vec![1, 2, 3]; let mut w: Vec<i32> = Vec::new(); w.push(4);与数组的取舍:长度编译期已知且小用
[T; N](栈上、零开销),需要增长或长度依赖运行时数据用Vec增删:
push尾部追加,pop尾部取出返回Option<T>,insert与remove按下标操作并整体挪动后面的元素rustlet mut v = vec![1, 2, 3]; v.push(4); // [1, 2, 3, 4] v.remove(0); // [2, 3, 4],后面元素前移 let last = v.pop(); // Some(3),v 变为 [2, 4]访问:下标越界直接
panic,get返回Option交由调用方处理rustlet v = vec![1, 2, 3]; let a = v[0]; // 越界时 panic let b = v.get(3); // None遍历:
&v只读,&mut v可写,直接v会消费元素rustfor x in &v { } // x 是 &i32 for x in &mut v { *x += 1; } for x in v { } // v 被移走,之后不可用常用方法:
len、is_empty、contains、clear、sort,配合迭代器能替代多数手写循环rustlet mut v = vec![3, 1, 2]; v.sort(); // [1, 2, 3] let d: Vec<i32> = v.iter().map(|x| x * 2).collect(); // [2, 4, 6]存放不同类型的常见做法:定义
enum让变体承载各类型,再放进Vec,见Rust 枚举与错误处理
HashMap
键值对容器:键唯一,平均
O(1)查找,迭代顺序不确定创建与插入:
HashMap::new()配合insert,键已存在时新值覆盖旧值,返回被覆盖的Option<V>rustuse std::collections::HashMap; let mut scores = HashMap::new(); scores.insert("alice", 90); let old = scores.insert("alice", 95); // Some(90)取值:
get按 key 借用,返回Option<&V>,match或if let解包后使用rustif let Some(score) = scores.get("alice") { println!("{score}"); }计数的惯用写法:
entry拿到键的占位枚举,or_insert在键不存在时先写入默认值,再就地修改rustlet text = "a b a c a b"; let mut counts: HashMap<&str, i32> = HashMap::new(); for word in text.split_whitespace() { *counts.entry(word).or_insert(0) += 1; }遍历与删除:
for (k, v) in &map遍历,remove按键删除并返回Option<V>所有权:
insert把键与值移入HashMap,之后原变量按所有权规则处理,Copy类型则直接复制对键的要求:必须实现
Eq与Hash,String、整型、&str与元组满足,浮点类型不满足需要按键有序遍历或做范围查询时改用
BTreeMap
HashSet
只存键不存值:成员判断平均
O(1),用于去重与已访问标记rustuse std::collections::HashSet; let mut set = HashSet::new(); let first = set.insert(1); // true let dup = set.insert(1); // false,条目不变 set.contains(&1); // true集合运算:
intersection交集、union并集、difference差集,返回迭代器rustlet a: HashSet<i32> = vec![1, 2, 3].into_iter().collect(); let b: HashSet<i32> = vec![2, 3, 4].into_iter().collect(); for x in a.intersection(&b) { } // 依次产出 2, 3
其他集合
VecDeque<T>:双端队列,头部与尾部都能O(1)进出,队列与滑动窗口用BTreeMap<K, V>与BTreeSet<T>:按键有序存储,支持范围查询与有序遍历LinkedList<T>:双向链表,缓存不友好也没有随机访问,多数场景应改用Vec或VecDeque
输入输出
打印:
print!不换行,println!换行,eprintln!输出到标准错误,{}按显示格式,{:?}按调试格式rustprintln!("x = {}, y = {}", 1, 2); println!("{:?}", vec![1, 2]); // 集合类型用调试格式打印读一行:
stdin().read_line把一行读进String并返回io::Result,expect在出错时终止并报告rustuse std::io::stdin; let mut line = String::new(); stdin().read_line(&mut line).expect("读入失败"); let n: i32 = line.trim().parse().expect("不是整数");trim去掉行尾换行与两端空白,parse把&str解析成数字并返回Result,错误处理见Rust 枚举与错误处理
读入并回显的完整例子:
print!不带换行时要手动flush才能及时显示rustuse std::io::{stdin, stdout, Write}; print!("你的名字: "); stdout().flush().unwrap(); let mut name = String::new(); stdin().read_line(&mut name).unwrap(); println!("你好, {}", name.trim());