rust 源码版本: 1.78.0
文件位置
HashMap
和 HashSet
位于 std
库中, 而其余的容器则在 alloc
库中,由 std
库重导出。
原因参考 Move HashMap to liballoc · Issue #27242 · rust-lang/rust · GitHub
hashbrown itself is
#[no_std]
since it uses a non-HashDOS-safe hasher. The std shim is what adds the SipHash hasher which depends on randomness and TLS.
可能是哈希的实现方法依赖于系统的随机数发生器,所以 HashMap
和 HashSet
需要放到 std
库中。
实现
type
1 | pub struct HashMap<K, V, S = RandomState> { |
其中 K
、V
、S
分别表示键类型、值类型和哈希函数。std
的 hashMap
实际上的底层实现实际上是 GitHub - rust-lang/hashbrown 。
由于 hashMap
内基本为 hashbrown
的封装,所以其中的常规函数和迭代器就省略不讲,下面讲一下其中有趣的地方。
issue
HashMap的默认哈希函数为 SipHash ,用的是 SipHash-1-3
。
下面这这段代码来自 rust/library/std/src/hash/random.rs,为默认哈希函数的实现。
1 | pub fn new() -> RandomState { |
哈希函数参数的初始化从注释中能看出有点说法,最初参数是在初始化随机数生成器后,调用两次随机数生成器分别生成k0
,k1
两个参数,最初的代码为:
1 | pub fn new() -> RandomState { |
-
rand 代码位于 rust/src/libstd/rand/mod.rs
-
randomState 代码位于 rust/src/libstd/collections/hash/map.rs
在 #27243 和 #31356 中提出,每个线程使用固定的随机数种子对于初始化速度有显著的提升(64倍),最终在 #33318 中这个改动被合入,代码为:
1 | pub fn new() -> RandomState { |
这个 mr 的做法为为每个线程生成两个随机种子,之后的同一线程初始化的 HashMap 都使用固定的随机种子(这个操作有点骚气)。然后在 #36481 中潜在的问题被逮住了,这种做法会被 Hash DoS attack。具体是这样的
由于每个线程中创建的 hashMap 使用同一个随机种子,考虑如下代码
1 | let first_map: HashMap<u64, _> = (0..900000).map(|i| (i, ())).collect(); |
修复:
variance
为了让生命周期更为智能, rust 引入了子类型和变异性(subtyping and variance),具体来说可以参考以下两个链接。
Subtyping and Variance - The Rustonomicon
可以看出 HashMap 的各种迭代器对于生命周期是协变的。
1 | fn assert_covariance() { |