Java中HashMap底层原理、ConcurrentHashMap

HashMap底层原理

HashMap基于哈希表的Map接口实现。HashMap存储的是键值对(key-value)映射,并且允许使用null作为键(key)和值(value)。要注意的是,HashMap只允许有一条记录的键为null,但允许多条记录的值为null。

HashMap的底层实现主要由数组、链表、红黑树组成。当我们使用put(key, value)方法向HashMap中添加元素时,会首先计算key的哈希值,然后使用这个哈希值对数组长度取模,得到的结果就是该元素在数组中的下标位置。如果在这个位置上已经存在其他元素(哈希冲突),就会使用链表来存储这些具有相同哈希值的元素。在JDK 1.8及以后的版本中,当链表长度超过一定阈值(默认为8)时,链表会转换为红黑树以提高查询效率。

  1. 数组:HashMap的主干是数组。当我们要新增或查找某个元素时,首先通过哈希函数(hash函数)计算出该元素的关键字对应的哈希值,然后利用这个哈希值对数组长度取模(在Java中,这个过程实际上是使用哈希值的高位和低位进行异或运算,然后对数组长度进行取余,这被称为位运算),得到的结果即为该元素在数组中的下标位置。
  2. 链表:当两个或多个元素经过哈希函数计算后得到相同的下标位置时,就发生了哈希冲突。为了解决哈希冲突,HashMap采用链表的方式存储这些具有相同哈希值的元素。在JDK 1.8之前,HashMap使用的是头插法插入新节点,而在JDK 1.8及之后,HashMap改为了尾插法。
  3. 红黑树:随着链表长度的增加,查找效率会逐渐降低。为了提高查找效率,在JDK 1.8中,当链表长度超过8且数组长度超过64时,HashMap会将链表转换为红黑树。红黑树是一种自平衡的二叉搜索树,它可以保证在最坏情况下,查找、插入和删除的时间复杂度都是O(log n)。
  4. 扩容机制:当HashMap中的元素数量超过其容量的一定比例(负载因子,默认为0.75)时,HashMap会进行扩容操作。扩容操作是通过将原数组长度翻倍,并重新计算每个元素的哈希值和新位置来完成的。这个过程会涉及到大量的数据迁移和哈希计算,因此扩容操作是比较耗时的。

HashMap不是线程安全的,也就是说,如果有多个线程同时修改HashMap,可能会导致数据的不一致。如果需要在多线程环境下使用HashMap,可以考虑使用Collections.synchronizedMap方法来使HashMap具有线程安全的能力,或者使用ConcurrentHashMap类。


HashMap和Hashtable的区别

HashMap和Hashtable在Java中都是用于存储键值对的集合以下是它们之间的主要区别:

  1. 继承关系:HashMap继承自AbstractMap类,而Hashtable继承自Dictionary类。这种继承关系决定了它们之间的部分功能差异。
  2. 线程安全性Hashtable是线程安全的,它的每个方法中都加入了synchronized修饰符,因此多个线程同时访问时,不会出现问题。而HashMap则是非线程安全的,如果在多线程环境下使用HashMap,需要额外的同步机制来确保线程安全。
  3. 对null的支持:Hashtable既不支持Null key也不支持Null value。如果尝试插入或获取null键或值,Hashtable会抛出NullPointerException。然而,HashMap允许有一个null键和任意数量的null值。
  4. 扩容机制:HashMap在默认情况下,当容量和负载因子的乘积大于元素个数时,会进行扩容操作,扩容一般是将原来的HashMap数组翻倍。而Hashtable在每次扩容时,容量会翻倍再加1。
  5. 数据结构:HashMap在JDK 1.8及以后的版本中,当链表长度超过一定阈值时,会将链表转换为红黑树以提高查询效率。而Hashtable在JDK 8及以前的版本中,只使用链表来解决哈希冲突,没有使用红黑树。
  6. 性能:由于Hashtable是线程安全的,其性能相对较低。而HashMap在单线程环境下性能更优,但在多线程环境下需要额外的同步机制来确保线程安全。

ConcurrentHashMap

ConcurrentHashMap 是Java中一个线程安全且高效的哈希表实现,它是从Java 5开始引入的,作为HashTable的线程安全且性能更优的替代品。在Java 8中,ConcurrentHashMap经历了重大的内部结构和实现上的改进,使其在高并发环境下的表现更加出色。

在Java 7中,ConcurrentHashMap采用分段锁(Segment)机制来实现线程安全。它将整个哈希表分成多个Segment(默认为16个),每个Segment相当于一个小型的哈希表,并且拥有自己的锁。这意味着在进行读写操作时,只要操作的数据分布在一个Segment中,就只需要锁定这个Segment,其他Segment可以同时被其他线程访问,大大提高了并发效率。

Java 8对ConcurrentHashMap进行了重大改造,引入了CAS(Compare and Swap)无锁算法、Node节点以及红黑树等数据结构,放弃了原有的Segment设计。

  1. CAS操作与Node节点:每个桶(bucket)现在是一个链表或者红黑树的头节点。对于非null的桶,通过CAS操作来保证原子性地修改桶中的引用。
  2. Tree化:当链表长度超过一定阈值(默认为8)时,会将链表转换为红黑树,以提高查找效率。同样,当红黑树的节点数量降到一定程度时,又会退化回链表,减少空间开销和维护成本。
  3. 大小可变:Java 8的ConcurrentHashMap支持动态扩容。扩容时,会创建一个新的、容量更大的table,并将原table的数据重新映射到新table中,这一过程也是并发进行的。
  4. 线程安全:除了使用CAS操作外,Java 8的ConcurrentHashMap还使用了若干个volatile变量来确保可见性和原子性,从而在很多情况下避免了加锁操作,进一步提升了并发性能。

使用场景:ConcurrentHashMap非常适合在多线程环境下作为共享数据存储结构,比如作为缓存、计数器等。它的高效并发访问能力使得它成为处理高并发请求时的理想选择。

  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值