HashMap 是 Java 集合框架中非常重要的实现类,基于哈希表(Hash Table)实现,用于存储键值对(key-value)映射,提供快速的插入、删除和查找操作。以下是对 HashMap 的详细介绍:

一、HashMapHashMap 的基本特性

  1. 存储结构:基于数组(哈希桶)+ 链表 / 红黑树实现(Java 8 及以上)
  2. 无序性:存储顺序与插入顺序无关(与 TreeMap 的有序性不同)
  3. 键唯一性:key 不允许重复,若插入相同 key,新 value 会覆盖旧 value
  4. null 支持:允许 key 为 null(仅允许一个),允许多 value 为 null(允许多个)
  5. 非线程安全:多线程环境下可能出现并发问题(如需线程安全,可使用 ConcurrentHashMap 或 Collections.synchronizedMap()
  6. 初始容量与负载因子
    • 初始容量:默认 16(哈希桶数组的初始长度)
    • 负载因子:默认 0.75(扩容阈值 = 容量 × 负载因子,当元素数量超过阈值时触发扩容)

二、HashMap 的底层原理

1. 哈希表结构(Java 8+)

HashMap 的核心是 哈希桶数组Node<K,V>[] table),每个数组元素是一个链表或红黑树的头节点:

  • 当链表长度超过阈值(默认 8)且数组容量 ≥ 64 时,链表会转为红黑树(提高查询效率)
  • 当红黑树节点数量少于 6 时,会退化为链表(节省空间)

plaintext

哈希桶数组: [Node1, Node2, ..., Node16]
               ↓      ↓
             链表/红黑树  链表/红黑树
2. 核心操作原理
(1)存储键值对(put 方法
  1. 计算哈希值:对 key 调用 hashCode() 计算哈希值,再通过扰动函数(减少哈希冲突)得到最终哈希值
  2. 确定索引:根据哈希值和数组长度计算数组索引((n - 1) & hash,等价于取模运算,效率更高)
  3. 处理哈希冲突
    • 若索引位置为空,直接插入新节点
    • 若索引位置已有节点:
      • 若 key 相同(equals() 为 true),覆盖 value
      • 若 key 不同,插入链表 / 红黑树(链表长度达标则转红黑树)
  4. 扩容检查:插入后若元素数量超过阈值,触发扩容(数组长度翻倍,重新计算所有元素的索引并迁移)
(2)查询键值对(get 方法)
  1. 计算 key 的哈希值和数组索引
  2. 遍历索引位置的链表 / 红黑树,通过 equals() 匹配 key,返回对应的 value
  3. 若未找到,返回 null
(3)扩容机制
  • 当元素数量 > 容量 × 负载因子时,数组容量翻倍(始终为 2 的幂次方)
  • 扩容时需重新计算所有元素的索引(因容量变化,(n-1) & hash 结果改变)
  • Java 8 优化了扩容时的索引计算(通过高位运算判断,避免重新哈希)

三、关键方法与示例

1. 常用方法

java

运行

import java.util.HashMap;
import java.util.Map;

public class HashMapExample {
    public static void main(String[] args) {
        // 创建 HashMap
        Map<String, Integer> map = new HashMap<>();

        // 添加元素(put)
        map.put("apple", 10);
        map.put("banana", 20);
        map.put("orange", 30);

        // 查找元素(get)
        int count = map.get("apple"); // 10

        // 替换元素(put 或 replace)
        map.put("apple", 15); // 覆盖旧值
        map.replace("banana", 25); // 仅替换已存在的 key

        // 移除元素(remove)
        map.remove("orange");

        // 检查 key 是否存在(containsKey)
        boolean hasBanana = map.containsKey("banana"); // true

        // 遍历元素
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        // 其他常用方法
        int size = map.size(); // 元素数量
        boolean isEmpty = map.isEmpty(); // 是否为空
        map.clear(); // 清空所有元素
    }
}
2. 自定义对象作为 Key

当使用自定义对象作为 key 时,必须重写 hashCode() 和 equals() 方法,否则会导致无法正确查找或删除元素:

java

运行

class Student {
    private String id;
    private String name;

    // 构造方法、getter、setter 省略

    // 重写 hashCode() 和 equals()
    @Override
    public int hashCode() {
        return id.hashCode(); // 基于唯一标识 id 计算哈希值
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Student student = (Student) obj;
        return id.equals(student.id); // 基于 id 判断是否相同
    }
}

// 使用自定义对象作为 key
Map<Student, Integer> studentScores = new HashMap<>();
studentScores.put(new Student("1001", "Alice"), 90);

四、HashMap 与其他 Map 的对比

实现类 底层结构 有序性 线程安全 允许 null
HashMap 数组 + 链表 / 红黑树 无序
Hashtable 数组 + 链表 无序 是(低效)
TreeMap 红黑树 有序 key 不允许
LinkedHashMap 哈希表 + 双向链表 插入 / 访问顺序

五、注意事项与最佳实践

  1. 初始容量设置:若已知元素数量,可指定初始容量(如 new HashMap<>(100)),减少扩容次数
  2. 哈希冲突避免:自定义 key 时,确保 hashCode() 分布均匀,减少哈希冲突
  3. 线程安全:多线程环境下优先使用 ConcurrentHashMap,而非 Hashtable
  4. 遍历效率
    • 遍历 key:for (K key : map.keySet())
    • 遍历键值对:for (Map.Entry<K,V> entry : map.entrySet())(推荐,效率更高)
  5. Java 8 改进:引入红黑树优化长链表的查询性能(时间复杂度从 O (n) 提升至 O (log n))

HashMap 凭借高效的哈希算法和动态扩容机制,成为日常开发中最常用的 Map 实现类,适用于大多数键值对存储场景。理解其底层原理有助于更好地使用和优化它。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐