怎么理解Java中HashMap底层实现、加载因子、容量值及死循环

发布时间:2021-11-02 11:29:34 作者:iii
来源:亿速云 阅读:169

这篇文章主要介绍“怎么理解Java中HashMap底层实现、加载因子、容量值及死循环”,在日常操作中,相信很多人在怎么理解Java中HashMap底层实现、加载因子、容量值及死循环问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理解Java中HashMap底层实现、加载因子、容量值及死循环”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

HashMap 简介

HashMap是一个基于哈希表实现的无序的key-value容器,它键和值允许设置为 null,同时它是线程不安全的。

HashMap 底层实现

红黑树简介

红黑树是一种特殊的平衡二叉树,它有如下的特征:

所以红黑树的时间复杂度为: O(lgn)。

怎么理解Java中HashMap底层实现、加载因子、容量值及死循环

jdk1.8:数组+链表+红黑树

HashMap的底层首先是一个数组,元素存放的数组索引值就是由该元素的哈希值(key-value中key的哈希值)确定的,这就可能产生一种特殊情况——不同的key哈希值相同。

在这样的情况下,于是引入链表,如果key的哈希值相同,在数组的该索引中存放一个链表,这个链表就包含了所有key的哈希值相同的value值,这就解决了哈希冲突的问题。

但是如果发生大量哈希值相同的特殊情况,导致链表很长,就会严重影响HashMap的性能,因为链表的查询效率需要遍历所有节点。于是在jdk1.8引入了红黑树,当链表的长度大于8,且HashMap的容量大于64的时候,就会将链表转化为红黑树。

// jdk1.8  // HashMap#putVal  // binCount 是该链表的长度计数器,当链表长度大于等于8时,执行树化方法  // TREEIFY_THRESHOLD = 8  if (binCount >= TREEIFY_THRESHOLD - 1)      treeifyBin(tab, hash);  // HashMap#treeifyBin      final void treeifyBin(Node<K,V>[] tab, int hash) {      int n, index; Node<K,V> e;      // MIN_TREEIFY_CAPACITY=64      // 若 HashMap 的大小小于64,仅扩容,不树化      if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)          resize();      else if ((e = tab[index = (n - 1) & hash]) != null) {          TreeNode<K,V> hd = null, tl = null;          do {              TreeNode<K,V> p = replacementTreeNode(e, null);              if (tl == null)                  hd = p;              else {                  p.prev = tl;                  tl.next = p;              }              tl = p;          } while ((ee = e.next) != null);          if ((tab[index] = hd) != null)              hd.treeify(tab);      }  }

加载因子为什么是0.75

所谓的加载因子,也叫扩容因子或者负载因子,它是用来进行扩容判断的。

假设加载因子是0.5,HashMap初始化容量是16,当HashMap中有16 * 0.5=8个元素时,HashMap就会进行扩容操作。

而HashMap中加载因子为0.75,是考虑到了性能和容量的平衡。

由加载因子的定义,可以知道它的取值范围是(0, 1]。

// 构造函数  public HashMap(int initialCapacity, float loadFactor) {      // &hellip;&hellip;      this.loadFactor = loadFactor;// 加载因子      this.threshold = tableSizeFor(initialCapacity);  }  /**   * Returns a power of two size for the given target capacity.返回2的幂   * MAXIMUM_CAPACITY = 1 << 30   */  static final int tableSizeFor(int cap) {      int n = cap - 1;      n |= n >>> 1;      n |= n >>> 2;     n |= n >>> 4;      n |= n >>> 8;      n |= n >>> 16;      return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;  }

HashMap 的容量为什么是2的 n 次幂

HashMap的默认初始容量是16,而每次扩容是扩容为原来的2倍。这里的16和2倍就保证了HashMap的容量是2的n次幂,那么这样设计的原因是什么呢?

原因一:与运算高效

与运算&,基于二进制数值,同时为1结果为1,否则就是0。如1&1=1,1&0=0,0&0=0。使用与运算的原因就是对于计算机来说,与运算十分高效。

原因二:有利于元素充分散列,减少 Hash 碰撞

在给HashMap添加元素的putVal函数中,有这样一段代码:

// n为容量,hash为该元素的hash值  if ((p = tab[i = (n - 1) & hash]) == null)      tab[i] = newNode(hash, key, value, null);

它会在添加元素时,通过i = (n - 1) & hash计算该元素在HashMap中的位置。

当 HashMap 的容量为 2 的 n 次幂时,他的二进制值是100000&hellip;&hellip;(n个0),所以n-1的值就是011111&hellip;&hellip;(n个1),这样的话(n - 1) & hash的值才能够充分散列。

举个例子,假设容量为16,现在有哈希值为1111,1110,1011,1001四种将被添加,它们与n-1(15的二进制=01111)的哈希值分别为1111、1110、1110、1011,都不相同。

而假设容量不为2的n次幂,假设为10,那么它与上述四个哈希值进行与运算的结果分别是:0101、0100、0001、0001。

可以看到后两个值发生了碰撞,从中可以看出,非2的n次幂会加大哈希碰撞的概率。所以 HashMap 的容量设置为2的n次幂有利于元素的充分散列。

HashMap 是如何导致死循环的

HashMap会导致死循环是在jdk1.7中,由于扩容时的操作是使用头插法,在多线程的环境下可能产生循环链表,由此导致了死循环。在jdk1.8中改为使用尾插法,避免了该死循环的情况。

到此,关于“怎么理解Java中HashMap底层实现、加载因子、容量值及死循环”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. JAVA中HASHMAP怎么实现死循环
  2. 怎么理解Oracle集群因子

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

hashmap java

上一篇:好用的CSS工具有哪些

下一篇:VS2010扩展辅助工具有哪些

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》