Skip to content

HashMap高级特性

> 学习自定义类型作为键的方法,理解BTreeMap与HashMap的区别。

自定义类型作为 HashMap 的键

需要实现的 Trait

要使自定义类型可以作为 HashMap 的键,必须实现:

1. Eq + PartialEq - 用于比较键是否相等
2. Hash - 用于计算哈希值

┌─────────────────────────────────────────────────────┐
│           自定义类型作为键的要求                     │
├─────────────────────────────────────────────────────┤
│                                                     │
│  必须实现的 Trait:                                  │
│  ├── PartialEq - 判断两个值是否相等                 │
│  ├── Eq - 完全相等关系(自反、对称、传递)          │
│  └── Hash - 计算哈希值                              │
│                                                     │
│  推荐派生:                                          │
│  #[derive(Debug, Clone, PartialEq, Eq, Hash)]       │
│                                                     │
│  注意事项:                                          │
│  • 字段都必须是 Hash 的                              │
│  • 浮点数不能直接作为键(f32/f64 不实现 Hash)        │
│  • 哈希值相同的键可能冲突                          │
│                                                     │
└─────────────────────────────────────────────────────┘

使用 derive 自动派生

rust
use std::collections::HashMap;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct User {
    id: u32,
    name: String,
}

fn main() {
    let mut users = HashMap::new();

    let user1 = User {
        id: 1,
        name: String::from("Alice"),
    };

    let user2 = User {
        id: 2,
        name: String::from("Bob"),
    };

    users.insert(user1.clone(), "alice@example.com");
    users.insert(user2.clone(), "bob@example.com");

    // 使用相同的键查找
    let lookup = User {
        id: 1,
        name: String::from("Alice"),
    };

    match users.get(&lookup) {
        Some(email) => println!("找到邮箱:{}", email),
        None => println!("未找到用户"),
    }
}
▶ Run

手动实现 Hash

rust
use std::collections::HashMap;
use std::hash::{Hash, Hasher};

#[derive(Debug, PartialEq, Eq)]
struct Point {
    x: i32,
    y: i32,
}

// 手动实现 Hash
impl Hash for Point {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.x.hash(state);
        self.y.hash(state);
    }
}

fn main() {
    let mut map = HashMap::new();

    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };

    map.insert(p1, "点 A");
    map.insert(p2, "点 B");

    // 查找
    let lookup = Point { x: 1, y: 2 };
    println!("{:?}", map.get(&lookup));  // Some("点 A")
}
▶ Run

注意事项:浮点数不能作为键

rust
use std::collections::HashMap;

fn main() {
    // ❌ 错误:f64 不实现 Hash
    // let mut map: HashMap<f64, &str> = HashMap::new();
    // map.insert(3.14, "pi");

    // ✅ 解决方案 1:使用包装类型
    use std::num::NonZeroU32;

    // ✅ 解决方案 2:转换为整数(如果适用)
    let mut map: HashMap<i64, &str> = HashMap::new();
    map.insert((3.14 * 100.0) as i64, "pi");

    // ✅ 解决方案 3:使用 BTreeMap(允许自定义比较)
    use std::collections::BTreeMap;
    let mut btree: BTreeMap<f64, &str> = BTreeMap::new();
    btree.insert(3.14, "pi");
}
▶ Run

小结

  • 自定义类型作为键:必须实现 Hash + Eq + PartialEq,可用 #[derive] 派生
  • 浮点数不能作为HashMap键:f32/f64不实现Hash,可转整数或用BTreeMap
  • BTreeMap:有序存储,按键排序遍历,适合需要顺序的场景

练习题

详见:练习题