Rust字典(Rust HashMap、Rust BTreeMap)(std::collections::HashMap、std::collections::BTreeMap)哈希表桶、哈希表槽

参考文章:哈希表(Hash Table)实现原理(哈希映射HashMap、哈希函数(取余法、乘法法、加法法)、索引bucket桶、哈希冲突、链地址法、开放地址法(线性探测法)、负载因子、哈希表扩容)

Rust字典(Rust HashMap、Rust BTreeMap)

在 Rust 中,常见的字典类型是 HashMapBTreeMap,它们都属于标准库中的 std::collections 模块。它们主要的区别在于底层实现和使用场景。下面是这两种类型的详细解释:

1. HashMap

- 实现

基于哈希表(hash table)实现,键通过哈希函数映射到不同的桶中。

- 特性
1. 快速查找:平均 O(1) 的时间复杂度,适用于需要频繁插入、删除和查找键值对的场景。
2. 无序:键值对的顺序不保证,哈希表的存储顺序是由哈希值决定的。
3. 可以动态调整大小,以应对负载因子变化。
- 用法
#![allow(dead_code)] // 忽略全局dead code,放在模块开头!
#![allow(unused_variables)] // 忽略未使用变量,放在模块开头!

// #[derive(Debug)]

fn main() {
    use std::collections::HashMap;

    let mut map = HashMap::new();
    map.insert("a", 1);
    map.insert("b", 2);

    if let Some(value) = map.get("a") {
        println!("Found: {}", value);
    }
}

在这里插入图片描述

“桶”与“槽”

在哈希表(例如 Rust 的 HashMap)的实现中,(bucket)是用来存储键值对的容器或位置。每个桶存储着哈希表中一个特定的“槽”中的元素,多个元素可能会被存放到同一个桶中。这种设计的目的是为了优化哈希表的性能,使得查找、插入和删除操作能够在常数时间复杂度(O(1))内完成。

如何理解桶?
  1. 哈希函数:当你插入一个键值对时,哈希表首先会通过一个哈希函数计算出该键的哈希值。哈希值是一个整数,表示该键在哈希表中的位置。

  2. 桶的数量:哈希表通常将桶的数量设置为一个固定的大小,这个大小可能是基于哈希表的当前大小动态扩展的。哈希表会根据哈希值将键映射到相应的桶。

  3. 桶的作用:每个桶是一个容器,桶中可以存放一个或多个键值对。哈希表通过哈希值来确定要查找哪个桶,进而查找该桶中的键值对。

  4. 桶冲突(碰撞):当多个键的哈希值相同,或者它们的哈希值被映射到相同的桶时,就会发生哈希碰撞。为了处理这种情况,哈希表通常会使用一种方法来将多个元素存储在同一个桶中,常见的解决方案有:

    • 链式法(Chaining):每个桶实际上是一个链表(或者其他数据结构),多个键值对就可以在同一个桶中按链表的形式存储。
    • 开放寻址法(Open Addressing):当发生碰撞时,哈希表会寻找另一个空桶来存储这个元素。
例子:假设我们要插入一些键值对

假设有一个简单的哈希表,桶的数量是 5(这里只是为了简化说明),哈希函数计算出来的哈希值是一个数字。键 "apple" 的哈希值计算为 2,键 "banana" 的哈希值计算为 2(假设发生碰撞),那么它们都会被放入桶 2 中。

桶 0: []
桶 1: []
桶 2: [("apple", 1), ("banana", 2)]  <-- 这里发生了哈希碰撞
桶 3: []
桶 4: []

为了有效管理这些碰撞,HashMap 会使用链表等数据结构来存储多个哈希值相同的键值对。

为什么桶重要?

桶的设计使得哈希表能够有效地组织数据并在平均常数时间内执行操作。如果没有桶,哈希表可能会退化成一个线性查找的结构,失去哈希表的效率优势。

在 Rust 的 HashMap 中,桶的数量和哈希函数一起决定了哈希表的性能。当碰撞较多时,哈希表可能会进行扩展,增加桶的数量,以保持较好的性能。

2. BTreeMap

- 实现

基于平衡二叉树(通常是红黑树)实现,确保键按顺序排列。

- 特性
1. 有序:键值对按键的排序顺序存储(默认是升序),可以进行范围查询等操作。
2. 查找性能较差:查找、插入、删除的时间复杂度是 O(log n),比哈希表稍慢。
3. 适用于需要键有顺序的场景,如按键排序或按范围查询。
- 用法
#![allow(dead_code)] // 忽略全局dead code,放在模块开头!
#![allow(unused_variables)] // 忽略未使用变量,放在模块开头!

// #[derive(Debug)]

fn main() {
    use std::collections::BTreeMap;

    let mut map = BTreeMap::new();
    map.insert("apple", 3);
    map.insert("banana", 5);

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

在这里插入图片描述

为什么代码中key的类型是&&str

在这里插入图片描述

  1. map 是一个 BTreeMap<&'static str, i32> 类型的变量。
  2. 通过 &map 引用,Rust 会遍历一个 &BTreeMap<&'static str, i32>
  3. BTreeMap 中的键值对是 (key, value),其中 key&'static str,但是由于你在 for 循环中遍历的是 &map,所以 key 的类型实际上是 &&'static str

总结对比

- HashMap 更适用于对性能要求较高、但对键顺序不敏感的情况。
- BTreeMap 更适用于需要键排序或进行范围查询的情况,性能略逊色于 HashMap

选择哪种类型,取决于你的具体需求:是更关心插入查找速度,还是需要按顺序操作数据。

在 Rust 中,BTreeMap 是一个有序的映射类型,它按键排序。在你的代码中,map.insert("apple", 3)map.insert("banana", 5) 的键是字符串字面量 (&str 类型)。

为什么 key 的类型是 &&str

首先,让我们分析这段代码的关键部分:

use std::collections::BTreeMap;

let mut map = BTreeMap::new();
map.insert("apple", 3);
map.insert("banana", 5);

for (key, value) in &map {
    println!("{}: {}", key, value);
}
BTreeMap 的键值存储
  • BTreeMap 存储的是键值对。在你的例子中,map 存储的是 &str 类型的键和 i32 类型的值。
  • 由于 Rust 的字符串字面量(比如 "apple")是静态的,因此它们的类型是 &'static str。这意味着它们是对内存中某个静态位置的引用,类型是 &'static str
为何会有 &&str 的类型?
  • BTreeMap 中,键是通过引用存储的,因此每个键的类型实际上是 &'static str
  • 然而,for (key, value) in &map 循环时,&map 是对 BTreeMap 的一个引用。因此,key 的类型将会是 &&str

这是因为:

举个例子:

&map 的类型是 &BTreeMap<&'static str, i32>,所以你获取的每个 key&&'static str(即引用的引用)。

如何避免这个情况?

你可以使用 *key 来解引用 &&str 类型的 key,使其变为 &str

for (key, value) in &map {
    println!("{}: {}", *key, value); // 解引用 key
}

这样,key 就会是 &str 类型而不是 &&str

总结

key 的类型是 &&str 是因为你正在通过 &map 引用来遍历 BTreeMap,所以每个 key 实际上是 BTreeMap 中的键(&'static str)的引用,而你正在访问的是引用的引用,即 &&str。通过解引用 *key,你可以获取原始的 &str 类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dontla

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值