VecDeque<T>
Vec
可以实现在队列两端高效添加和移除元素。如果要把值保存在队列中间,Vec
的处理效率就相对较慢。
双端队列 std::collections::VecDeque<T>
:实现了环形缓冲区,支持前端和后端的高效添加和移除操作。
deque.push_front(value)
:在队列前面添加值。deque.push_back(value)
:在队列后面添加值。deque.pop_front()
:移除并返回队列前面的值,返回 Option<T>
,队列为空时返回 None
。deque.pop_back
:移除并返回队列后面的值,返回 Option<T>
,队列为空时返回 None
。deque.front()
和 deque.back()
:返回队列前端和后端元素的共享引用,返回 Option<T>
,队列为空时返回 None
。deque.front_mut()
和 deque.back_mut
:返回队列前端和后端元素的可修改引用引用,返回 Option<&mut T>
。队列在内存中不是连续存储的,并没有继承切片的所有方法。如果要在队列数据上执行向量和切片的操作,需要把 VecDeque
转换为 Vec
,执行完操作后,再转换回去。
Vec<T>
实现了 From<VecDeque<T>>
,因此 Vec::from(deque)
可以将队列转换为向量。
VecDeque<T>
实现了 From<Vec<T>>
,因此 VecDeque::from(vec)
可以将向量转换为队列。
// 通过指定元素来创建队列的方法
use std::collections::VecDeque;
let v = VecDeque::from(vec![1, 2, 3, 4]);
LinkedList<T>
链表的每个值都存储在独立的堆内存中,是一种存储序列值的方式。
std::collections::LinkedList<T>
是双向链表
支持
VecDeque
的部分方法:
LinkedList::new()
等支持把两个列表非常快速地组合到一起:list1.append(&mut list2)
,把 list2
中的元素都转移到 list1
中。vec
和 VecDeque
也支持 append
方法,但是会把很多值从一个堆阵列转移到另一个。
BinaryHeap<T>
BinaryHeap<T>
用于存储松散元素,最大值始终会冒泡到队列前端。
常用的方法:
heap.push(value)
:向堆中添加值。heap.pop()
:从堆中移除并返回最大值。返回类型为 Option<T>
,如果堆为空则返回 None
。heap.peek()
:返回堆中最大值的引用。返回类型为 Option<&T>
。也支持 Vec
的部分方法:
BinaryHeap::new()
;heap.len()
;heap.is_empty()
;heap.capacity()
;heap.clear()
;heap.append(&mut heap2)
。实现了内置特型 Ord
的任何类型,都可以保存在 BinaryHeap
中。
常用于制作工作队列:
定义一个任务结构体,基于优先级实现 Ord
,让高优先级任务大于低优先级任务。然后,创建一个 BinaryHeap
保存所有带完成的任务。可以调用.pop()
方法始终返回接下来要做的最重要的任务。
可以使用 while
循环消费:
while let Some(task) = heap.pop() {
handle(task);
}
BinaryHeap
是可迭代类型,也有自己的.iter()
方法,迭代器的顺序是任意的。
HashMap<K, V>
和 BTreeMap<K, V>
映射(map)是一种键 — 值对(称为条目)集合。
HashMap<K, V>
把键和值保存在一个分配在堆上的散列表里,因此要求键的类型 K
实现标准的散列和相等性特型 Hash
和 Eq
。
BTreeMap<K, V>
按照键的顺序存储条目,总体为树形结构,因此要求键的类型 K
实现 Ord
。
Rust 的标准库使用 B 树,而不是平衡二叉树。二叉树每次搜索所必须的次数,比 B 树少;但搜索 B 树的领域性(locality)更好。B 树可以使得内存访问的集合在一起,而不是分散到整个堆上。这样 CPU 缓存就很少错失,从而显著提高速度。
创建映射的方法:
HashMap::new()
和 BTreeMap::new()
创建新的空映射;iter.collect()
:用于从键 — 值对创建和填充新的 HashMap
或 BTreeMap
。iter
必须是一个 Iterator<Item=(K, V)>
的迭代器。HashMap::with_capacity(n)
:可以创建新的空散列表,至少能容纳 n
个条目。HashMap
单独支持容量相关的方法:hash_map.capacity()
、hash_map.reserve(additional)
和 hash_map.shrink_to_fit()
。HashMap
和
BTreeMap
共同支持以下核心方法:
map.len()
:返回条目数量。map.is_empty()
:在 map
没有条目时返回 true
。map.contains_key(&key)
:在映射又条目的键为 key
时返回 true
。map.get(&key)
:搜索 map
中具有给定 key
的条目。如果找到匹配的条目,返回 Some(r)
,其中 r
是相对应的引用。否则返回 None
。map.get_mut(&key)
:功能与上同,但是返回对值的可修改引用。对于值可以随意修改,但键鼠鱼映射本身,不能修改。map.insert(key, value)
:向 map
中插入条目 (key, value)
。如果映射中已经存在为 key
的条目,则会插入新的值 value
,覆盖原来的值。如果覆盖了原来的值,会返回原来的值,返回类型为 Option<V>
。map.extend(iterable)
:迭代 iterable
的 (K, V)
条目,并将每个键 — 值对插入 map
中。map.append(&mut map2)
:将 map2
的所有条目转移到 map
中,map2
会变为空。map.remove(&key)
:查找并移除 map
中键为 key
的条目。如果有值被移除,则返回移除的值。返回类型为 Option<V>
。map.clear()
:移除所有条目。map[&key]
:用方括号语法查询映射。如果不存在为 key
的条目,则会诧异,类似数组访问越界。BTreeMap<K, V>
因为可以按键排序并存储条目,还支持
btree_map.split_off(key)
:
btree_map
一分为二;key
的条目会留在 btree_map
中。BTreeMap<K, V>
包含其他条目。Entry
条目类型:用于消除多余的映射查找。
let record = student_map.entry(name.to_string()).or_insert_with(Student);
student_map.entry(name.to_string)
返回的 Entry
值类似于指向映射中某个位置的可修改引用;
如果引用为空,则.or_insert_with()
方法会插入一个新的 Student
。
Entry
是一个枚举类型:
// std::collections::hash_map
pub enum Entry<'a, K: 'a, V: 'a> {
Occupied(OccupiedEntry<'a, K, V>),
Vacant(VacantEntry<'a, K, V>)
}
创建 Entry
值的方法:map.entry(key)
。
返回键为 key
的 Entry
。
如果映射中没有,则返回一个空的 Entry
。
接收自己的可修改引用,返回生命期匹配的 Entry
:
pub fn entry<'a>(&'a mut self, key: K) -> Entry<'a, K, V>
如果映射的键是 String
类型,则不能给这个方法传入 &str
类型的引用。此时要给.entry()
方法传入真正的 String
。
填充空条目的方法:
map.entry(key).or_insert(value)
:必须保证 map
包含键为 key
的条目,必要时会给定默认 value
插入新条目。返回对新的或已有值的可修改引用。
// 举例:计算投票数
let mut vote_counts: HashMap<String, usize> = HashMap::new();
for name in ballots {
let count = vote_counts.entry(name).or_insert(0);
*count += 1; // count的类型为&mut usize。
}
map.entry(key).or_insert_with(default_fn)
:功能与上同,但是在需要创建新条目时会调用 default_fn()
函数,以产生默认值。如果 map
中已经存在了为 key
的条目,则不会用到 default_fn()
。
let mut word_occurrence: HashMap<String, HashSet<String>> = Hashmap::new();
for file in files {
for word in read_words(file)? {
let set = word_occurrence
.entry(word)
.or_insert_with(HashSet::new);
set.insert(file.clone());
}
}
HashMap
迭代器支持以任意顺序访问映射的条目;BTreeMap
迭代器,必须按键顺序访问条目。for (k, v) in map
):产生 (K, V)
对。这样会消费映射。for (k, v) in &map
):产生 (&K, &V)
对。for (k, v) in &mut map
):产生 (&K, &mut v)
对。.iter()
和.iter_mut()
:返回迭代器且产生值引用的方法;map.keys()
:返回产生键引用的迭代器。map.values()
:返回产生值引用的迭代器。map.values_mut()
:返回产生可修改值引用的迭代器。HashSet<T>
和 BTreeSet<T>
Set
集:支持快速测试成员关系而组织值。集中永远不会包含同一个值的多个副本。
let b1 = large_vector.contains("needle"); // 在向量中查找元素比较慢,需要检查每个元素
let b2 = large_hash_set.contains("needle"); // 在集中查找元素比较快,支持散列查找
集类似于只有键的映射。HashSet<T>
和 BTreeSet<T>
都是以的包装类型形式实现的:
HashSet::new()
和 BTreeSet::new()
创建新集。iter.collect()
可用于从任何迭代器创建新集。如果产生了重复值,则重复的值会被清除。HashSet::with_capacity(n)
创建空的 HashSet
,至少容纳 n
个值。HashSet<T>
和 BTreeSet<T>
共有的方法:
set.len()
:返回 set
中值的数量。set.is_empty()
:在集中不包含元素值返回 true
。set.contains(&value)
:在集中包含给定值 value
时返回 true
。set.insert(value)
:向集中添加 value
。如果添加成功就返回 true
,如果集里面已经存在同样的值就返回 false
。set.remove(&value)
:从集里面移除 value
。如果移除成功就返回 true
。如果没有这个值,则返回 false
。与映射类似,按引用查询值的方法也是通用的,只要可以从 T
借用到该类型。
for v in set
产生集的成员,并且消费集。for v in &set
产生集成员的共享引用。set.iter()
返回迭代器,产生 set
成员的引用。HashSet
迭代器支持以任意顺序访问映射的条目;BTreeSet
迭代器,必须按键顺序访问条目。取得存储在集中的实际值的方法:这些方法都返回 Option
,如果 set
不包含匹配的值,则返回 None
。
set.get(&value)
:返回 set
中等于 value
的成员的共享引用,返回类型是 Option<&T>
。set.take(&value)
:类似于 set.remove(&value)
,但返回移除的值,返回类型是 Option<T>
。set.replace(value)
:类似于 set.insert(value)
,但如果 set
中已经包含等于 value
的值,那么就返回原来的值。返回类型是 Option<T>
set1.intersection(&set2)
:返回同时包含在 set1
和 set2
中的所有值的迭代器。&set1 & &set2
:返回 set1
和 set2
的交集。set1.union(&set2)
:返回同时包含在 set1
和 set2
种,或只在 set1
或 set2
的值的迭代器。&set1 | &set2
:返回包含这两个集中所有值的新集。set1.difference(&set2)
:返回包含在 set1
但不包含在 set2
中的值的迭代器。&set1 - &set2
:返回包含在 set1
但不包含在 set2
的所有这些值的新集。set1.symmetric_difference(&set2)
:返回只包含在 set1
中,或只包含在 set2
中,但不同时包含在两个集中的值的迭代器。&set1 ^ &set2
:返回只包含在 set1
中,或只包含在 set2
中,但不同时包含在两个集中的值的新集。set1.is_disjoint(set2)
:如果交集为空,则返回 true
。set1.is_subset(set2)
:如果 set1
是 set2
的子集,则返回 true
。set1.is_superset(set2)
:如果 set1
是 set2
的超集,则返回 true
。==
和 !=
的相等性测试:两个集包相同的值,那么它们相等。std::hash::Hash
是标准库中可散列类型的特型。
一个值无论保存在哪里,或者怎么指向它,都应该有相同的散列码。
Box
与装箱的值有相同的散列码;vec
与包含所有其数据的切片 &ved[..]
有相同的散列码;String
与引用相同字符的 &str
有相同的散列码。结构体和枚举默认没有实现 Hash
,但可以派生一个实现:
#[derive(Clone, PartialEq, Eq, Hash)]
enum MN {
...
}
如果手工为一个类型实现了 PartialEq
,那么必须为它实现 Hash
。
hash
方法是泛型的。
计算散列码的完整协议:
use std::hash::{Hash, Hasher, BuildHasher};
fn compute_hash<B, T>(builder: &B, value: &T) -> u64
where B: BuildHasher, T: Hash
{
let mut hasher = builder.build_hasher(); // 算法开始
value.hash(&mut hasher); // 传入数据
hasher.finish() // 算法完成,产生u64值。
}
Rust 默认使用 SipHash-1-3
算法作为散列算法。只要有不同之处,每个散列表就会使用无法预测的键。因此可以防止 HashDoS
的拒绝服务攻击,即攻击者故意使用散列冲突来出发服务器中最差的性能。
如果为了想要更快的散列实现,而牺牲安全性,那么可以使用 fnv
包实现的 Fowler-Noll-Vo
散列算法。
// 在Cargo.toml中添加:
[dependencies]
fnv = "1.0"
// 导入映射和散列类型
extern crate fnv;
use fnv::{FnvHashMap, FnvHashSet}; // 代替HashMap和HashSet。
Rust 支持在 unsafe
块中实现 C++ 的数据结构:原始指针、手工内存管理、放置 new
、显示调用析构函数。
详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第十六章
原文地址