面试突击:说一下HashMap底层实现?及元素添加流程?

开发 前端
HashMap 是使用频率最高的数据类型之一,同时也是面试必问的问题之一,尤其是它的底层实现原理,既是常见的面试题又是理解 HashMap 的基石,所以重要程度不言而喻。

HashMap 是使用频率最高的数据类型之一,同时也是面试必问的问题之一,尤其是它的底层实现原理,既是常见的面试题又是理解 HashMap 的基石,所以重要程度不言而喻。

HashMap 底层实现

HashMap 在 JDK 1.7 和 JDK 1.8 的底层实现是不一样的,在 JDK 1.7 中,HashMap 使用的是数组 + 链表实现的,而 JDK 1.8 中使用的是数组 + 链表或红黑树实现的。HashMap 在 JDK 1.7 中的实现如下图所示:

HashMap 在 JDK 1.8 中的实现如下图所示:

我们本文重点来学习主流版本 JDK 1.8 中的 HashMap。HashMap 中每个元素称之为一个哈希桶(bucket),哈希桶包含的内容有 4 个:

  • hash 值
  • key
  • value
  • next(下一个节点)

HashMap 插入流程

HashMap 元素新增的实现源码如下(下文源码都是基于主流版本 JDK 1.8):

  1. public V put(K key, V value) { 
  2.     // 对 key 进行哈希操作 
  3.     return putVal(hash(key), key, value, falsetrue); 
  4. final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 
  5.                boolean evict) { 
  6.     Node<K,V>[] tab; Node<K,V> p; int n, i; 
  7.     // 哈希表为空则创建表 
  8.     if ((tab = table) == null || (n = tab.length) == 0) 
  9.         n = (tab = resize()).length; 
  10.     // 根据 key 的哈希值计算出要插入的数组索引 i 
  11.     if ((p = tab[i = (n - 1) & hash]) == null
  12.         // 如果 table[i] 等于 null,则直接插入 
  13.         tab[i] = newNode(hash, key, value, null); 
  14.     else { 
  15.         Node<K,V> e; K k; 
  16.         // 如果 key 已经存在了,直接覆盖 value 
  17.         if (p.hash == hash && 
  18.             ((k = p.key) == key || (key != null && key.equals(k)))) 
  19.             e = p; 
  20.         // 如果 key 不存在,判断是否为红黑树 
  21.         else if (p instanceof TreeNode) 
  22.             // 红黑树直接插入键值对 
  23.             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 
  24.         else { 
  25.             // 为链表结构,循环准备插入 
  26.             for (int binCount = 0; ; ++binCount) { 
  27.                 // 下一个元素为空时 
  28.                 if ((e = p.next) == null) { 
  29.                     p.next = newNode(hash, key, value, null); 
  30.                     // 转换为红黑树进行处理 
  31.                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 
  32.                         treeifyBin(tab, hash); 
  33.                     break; 
  34.                 } 
  35.                 //  key 已经存在直接覆盖 value 
  36.                 if (e.hash == hash && 
  37.                     ((k = e.key) == key || (key != null && key.equals(k)))) 
  38.                     break; 
  39.                 p = e; 
  40.             } 
  41.         } 
  42.         if (e != null) { // existing mapping for key 
  43.             V oldValue = e.value; 
  44.             if (!onlyIfAbsent || oldValue == null
  45.                 e.value = value; 
  46.             afterNodeAccess(e); 
  47.             return oldValue; 
  48.         } 
  49.     } 
  50.     ++modCount; 
  51.     // 超过最大容量,扩容 
  52.     if (++size > threshold) 
  53.         resize(); 
  54.     afterNodeInsertion(evict); 
  55.     return null

上述的源码都添加了相应的代码注释,简单来说 HashMap 的元素添加流程是,先将 key 值进行 hash 得到哈希值,根据哈希值得到元素位置,判断元素位置是否为空,如果为空直接插入,不为空判断是否为红黑树,如果是红黑树则直接插入,否则判断链表是否大于 8,且数组长度大于 64,如果满足这两个条件则把链表转成红黑树,然后插入元素,如果不满足这两个条件中的任意一个,则遍历链表进行插入,它的执行流程如下图所示:

为什么要将链表转红黑树?

JDK 1.8 中引入了新的数据结构红黑树来实现 HashMap,主要是出于性能的考量。因为链表超过一定长度之后查询效率就会很低,它的时间复杂度是 O(n),而红黑树的时间复杂度是 O(logn),因此引入红黑树可以加快 HashMap 在数据量比较大的情况下的查询效率。

哈希算法实现

HashMap 的哈希算法实现源码如下:

  1. static final int hash(Object key) { 
  2.     int h; 
  3.     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 

其中,key.hashCode() 是 Java 中自带的 hashCode() 方法,返回一个 int 类型的散列值,后面 hashCode 再右移 16 位,正好是 32bit 的一半,与自己本身做异或操作(相同为 0,不同为 1),主要是为了混合哈希值的高位和低位,增加低位的随机性,这样就实现了 HashMap 的哈希算法。

总结

HashMap 在 JDK 1.7 时,使用的是数组 + 链表实现的,而在 JDK 1.8 时,使用的是数组 + 链表或红黑树的方式来实现的,JDK 1.8 之所以引入红黑树主要是出于性能方面的考虑。HashMap 在插入时,会判断当前链表的长度是否大于 8 且数组的长度大于 64,如果满足这两个条件就会把链表转成红黑树再进行插入,否则就是遍历链表插入。

参考文档:https://tech.meituan.com/2016/06/24/java-hashmap.html

本文转载自微信公众号「Java面试真题解析」,可以通过以下二维码关注。转载本文请联系Java面试真题解析公众号。

 

责任编辑:武晓燕 来源: Java面试真题解析
相关推荐

2022-09-27 21:14:54

Spring事务传播机制

2022-06-29 11:01:05

MySQL事务隔离级别

2022-05-18 07:43:09

Exchange交换器JUC

2022-09-05 07:06:59

BeanSpring

2023-02-06 07:01:51

2022-07-20 07:29:55

TCPIP协议

2022-02-17 08:02:08

线程Java生命周期

2022-03-09 07:35:24

线程池线程参数

2023-11-29 08:00:53

JavaTreeMap底层

2022-04-13 14:43:05

JVM同步锁Monitor 监视

2024-02-27 15:23:48

RedLock算法Redis

2024-01-29 10:08:11

零拷贝Zero-copyCPU 拷贝

2023-09-12 14:56:13

MyBatis缓存机制

2021-07-28 10:08:19

类加载代码块面试

2023-02-02 07:06:10

2023-01-04 07:54:03

HashMap底层JDK

2022-06-07 12:03:33

Java内存模型

2022-06-06 15:33:20

线程Java释放锁

2023-02-18 13:34:14

Nacos健康检查机制

2023-02-08 08:32:41

轮询锁
点赞
收藏

51CTO技术栈公众号