HashMap、HashTable和ConcurrentHashMap

悲观者从机会中看到困难。乐观者从困难中看到机会。
——温斯顿·丘吉尔

HashMap

  • 实现: 基于哈希表的数据结构,使用链地址法解决哈希冲突。从 Java 8 开始,当链表长度超过一定阈值时,链表会转换成红黑树,以提高查询性能。

  • 线程安全: HashMap 是非线程安全的。在多线程环境下,如果多个线程同时对 HashMap 进行操作,可能会导致数据不一致或死循环等问题。

  • 性能: 由于 HashMap 没有同步机制,它在单线程环境中性能非常高。插入、删除和查找操作的平均时间复杂度为 O(1)。

  • 允许 null 键和值: HashMap 允许一个 null 键和多个 null 值。

    HashMap 的初始化

    HashMap 的初始化主要有以下两种方式:

    • 默认构造函数:使用默认容量(16)和负载因子(0.75)。
    • 指定初始容量和负载因子:用户可以指定初始容量和负载因子

    初始化流程:

  • 默认情况下,HashMap 的容量(initialCapacity)为 16,负载因子(loadFactor)为 0.75。这意味着当 HashMap 中的元素数量达到容量的 75% 时,便会触发扩容。

  • 构造函数只会设置容量和负载因子,并不会真正分配数组空间。真正分配数组是在第一次调用 put() 方法时发生的。

    put 流程

    put 方法是 HashMap 用来插入键值对的核心方法。put 的流程如下:

    1. 计算哈希值: 调用 hash(key) 方法计算 key 的哈希值,并通过 indexFor(hash, length) 方法计算出该键值对应在数组中的索引。
    2. 判断是否需要初始化数组: 如果数组 table 为空,进行第一次初始化,分配数组空间。
    3. 检查冲突: 根据计算出的索引位置,检查数组中是否已经存在元素。如果该位置没有元素(即 null),则直接插入。如果存在元素,则遍历链表(或红黑树)查找是否有相同的 key
    4. 插入新元素: 如果找到了相同的 key,则更新该 key 对应的值;如果没有找到相同的 key,则将新的 Entry(Java 8 之后是 Node)插入到链表的头部或转换为红黑树。
    5. 检查是否需要扩容: 插入后,检查当前 HashMap 的元素个数是否超过了扩容阈值(即容量 * 负载因子)。如果超过,则进行扩容。

    get 流程

    get 方法用于根据 key 获取对应的 value。其流程如下:

    1. 计算哈希值: 和 put 类似,首先计算 key 的哈希值,并通过 indexFor(hash, length) 方法计算出该键值对应在数组中的索引。
    2. 查找元素: 根据计算出的索引位置,找到对应的链表(或红黑树),遍历链表或树查找与 key 相同的元素。
    3. 返回结果: 如果找到了对应的 key,则返回对应的 value;如果没找到,则返回 null

Hashtable

  • 实现: 与 HashMap 类似,也是基于哈希表实现的,但它的所有方法都是同步的,使用了 synchronized 关键字来保证线程安全。
  • 线程安全: Hashtable 是线程安全的,多个线程可以安全地访问同一个 Hashtable 实例而不会导致数据不一致的问题。然而,由于同步的开销,在高并发情况下性能较低。
  • 性能: 由于同步机制,Hashtable 的性能要比 HashMap 低,尤其是在高并发环境下。
  • 不允许 null 键和值: Hashtable 不允许 null 键或 null 值,试图插入 null 键或值会抛出 NullPointerException

ConcurrentHashMap

  • 实现: 基于分段锁(Segmented Lock)或(从 Java 8 开始)CAS(Compare-And-Swap)机制的哈希表,设计上更适合高并发场景。在 Java 8 之前,ConcurrentHashMap 将整个哈希表分为多个段(Segment),每个段相当于一个小的哈希表,使用锁机制保证每个段的线程安全,多个线程可以并发访问不同的段。在 Java 8 及之后,ConcurrentHashMap 使用了更为细粒度的锁机制和无锁(CAS)操作,使其性能进一步提升。
  • 线程安全: ConcurrentHashMap 是线程安全的,并且在高并发环境中表现出色。它在读取操作时不需要锁,因此读取速度非常快。写操作使用了细粒度的锁或无锁机制,减少了锁争用,提高了并发性能。
  • 性能: 在高并发场景中,ConcurrentHashMap 的性能要远高于 Hashtable 和同步包装的 HashMap(如 Collections.synchronizedMap(new HashMap<>())。
  • 不允许 null 键和值: 与 Hashtable 类似,ConcurrentHashMap 也不允许 null 键或值。
posted @ 2025-03-28 10:57  Tsukinor  阅读(45)  评论(0)    收藏  举报