遍历 HashMaps 如何在内存中工作:Rust

How does iterating over HashMaps work in memory: Rust

我知道如何在 Rust 中迭代 HashMap,但是,我对它在内存中的工作方式有点困惑。我们如何迭代未按顺序存储在内存中的值?非常感谢在堆和堆栈级别详细解释下面的代码。

use std::collections::HashMap;

let name = vec![String::from("Charlie"), String::from("Winston"), String::from("Brian"), String::from("Jack")];
let age = vec![50, 5, 7, 21];

let mut people_ages: HashMap<String, i32> = name.into_iter().zip(age.into_iter()).collect();


for (key, value) in &people_ages {
    println!("{}: {}", key, value);
}

documentation, it is mentioned that the implementation relies on a C++ implementation of SwissTables 的介绍结束时。 此页面包含有关两种变体的插图:基于 « flat » 和 « node »。

这两个变体之间的主要区别在于指针稳定性。 在基于 « node » 的版本中,键值对一旦插入,就会将其地址保存在内存中,即使哈希被重新组织也是如此。 在 « flat » 版本中,一些 insertions/removals 可以使之前的键值对在内存中移动。

在 Rust 实现方面,我的经验不足,无法确定任何具体细节,但我尝试了这个基于您的简单示例。

use std::collections::HashMap;

fn main() {
    let name = vec![
        String::from("Charlie"),
        String::from("Winston"),
        String::from("Brian"),
        String::from("Jack"),
    ];
    let age = vec![50, 5, 7, 21];
    let mut people_ages: HashMap<String, i32> =
        name.into_iter().zip(age.into_iter()).collect();
    let mut keys = Vec::new();
    let mut values = Vec::new();
    for (key, value) in &people_ages {
        keys.push(key);
        values.push(value);
        let key_addr = key as *const String as usize;
        let value_addr = value as *const i32 as usize;
        println!("{:x} {:x} {}: {}", key_addr, value_addr, key, value);
    }
    // people_ages.insert("Bob".to_owned(), 4); // mutable and immutable borrow
    println!("keys: {:?}", keys);
    println!("values: {:?}", values);
}
/*
55e08ff8bd40 55e08ff8bd58 Brian: 7
55e08ff8bd20 55e08ff8bd38 Charlie: 50
55e08ff8bd00 55e08ff8bd18 Winston: 5
55e08ff8bce0 55e08ff8bcf8 Jack: 21
keys: ["Brian", "Charlie", "Winston", "Jack"]
values: [7, 50, 5, 21]
*/

被注释掉的行(插入)被拒绝,因为我们无法在保留对其内容的引用的同时更改哈希图。 因此,我猜测(我不确定)该实现不依赖于基于«节点»的变体,因为我们无法利用指针稳定性 它提供(由于 Rust 中的所有权模型),可能 它依赖于 « flat » 变体。

这意味着我们可以预期与相同散列关联的键值对在内存中紧密打包,并且迭代它们应该与迭代向量非常相似:规则级数(但是有一些跳过)对缓存预取非常友好。 打印地址往往会证实猜测(但测试不够完整),并显示倒退。