谈谈Hashmap的容量为什么是2的幂次问题

 更新时间:2020年9月24日 00:18  点击:1881

做为面试常考的问题之一,每次都答的模模糊糊,有必要了解一下,首先来看一下hashmap的put方法的源码

public V put(K key, V value) {
  if (key == null)    
   return putForNullKey(value);  //将空key的Entry加入到table[0]中
  int hash = hash(key.hashCode());  //计算key.hashcode()的hash值,hash函数由hashmap自己实现
  int i = indexFor(hash, table.length);//获取将要存放的数组下标
  /*
   * for中的代码用于:当hash值相同且key相同的情况下,使用新值覆盖旧值(其实就是修改功能)
   */
  for (Entry<K, V> e = table[i]; e != null; e = e.next) {//注意:for循环在第一次执行时就会先判断条件
   Object k;
   //hash值相同且key相同的情况下,使用新值覆盖旧值
   if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    V oldValue = e.value;
    e.value = value;
    //e.recordAccess(this);
    return oldValue;//返回旧值
   }
  }
  
  modCount++;
  addEntry(hash, key, value, i);//增加一个新的Entry到table[i]
  return null;//如果没有与传入的key相等的Entry,就返回null
 }

/**
  * "按位与"来获取数组下标
  */
 static int indexFor(int h, int length) {
  return h & (length - 1);
 }

hashmap始终将自己的桶保持在2的n次方,这是为什么?indexFor这个方法解释了这个问题

大家都知道计算机里面位运算是基本运算,位运算的效率是远远高于取余%运算的

举个例子:

2^n转换成二进制就是1+n个0,减1之后就是0+n个1,如16 -> 10000,15 -> 01111

那么根据&位运算的规则,都为1(真)时,才为1,那0≤运算后的结果≤15,假设h <= 15,那么运算后的结果就是h本身,h >15,运算后的结果就是最后四位二进制做&运算后的值,最终,就是%运算后的余数。

当容量一定是2^n时,h & (length - 1) == h % length

补充知识:HashMap容量和负载因子

HashMap底层数据结构是数组+链表,JDK1.8中还引入了红黑树,当链表长度超过8个时,会将链表转成红黑树,以提升其查找性能。那么,给出一个<key, value>节点,HashMap是如何确定这个节点应该放在具体哪个位置呢?(以JDK1.8为例)

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
  Node<K,V>[] tab; Node<K,V> p; int n, i;
  // HashMap没有被初始化,则先进行初始化
  if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
  // 节点所在index = (n - 1) & hash,该位置没有数据,则直接将新节点放在数组的index位置上
  if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
  else { // index上已经有节点了
    Node<K,V> e; K k;
    // 如果新key与原来的key一样,则e指向原节点p(后面会用新value替换e所指向的value)
    if (p.hash == hash &&
      ((k = p.key) == key || (key != null && key.equals(k)))) 
      e = p;
    // 如果该节点是树节点,则采用树的插入算法,插入新节点
    else if (p instanceof HashMap.TreeNode)
      e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    else { // 该节点是链表节点
      for (int binCount = 0; ; ++binCount) {
        // 将新节点插入到index所在链表的末端
        if ((e = p.next) == null) {
          p.next = newNode(hash, key, value, null);
          // 链表节点超过8个,则进行链表转树处理
          if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
            treeifyBin(tab, hash);
          break;
        }
        // 同样的,如果key已经存在的话,则不进行插入操作,而是后面进行value替换
        if (e.hash == hash &&
          ((k = e.key) == key || (key != null && key.equals(k))))
          break;
        p = e;
      }
    }
    // e != null的情况,就是key已经存在了,这里统一进行了新值value,替换旧值e.value的操作
    if (e != null) { // existing mapping for key
      V oldValue = e.value;
      if (!onlyIfAbsent || oldValue == null)
        e.value = value;
      afterNodeAccess(e);
      return oldValue;
    }
  }
  ++modCount;
  // 插入后数组size 大于阈值的话,需要进行扩容
  if (++size > threshold)
    resize();
  afterNodeInsertion(evict);
  return null;
}

看源码,节点落在数组中的index = (数组长度 - 1) & key的hashcode,如果该index上没有数据,则直接插到该index上,如果节点已经有数据了,则把新节点插入该index对应的链表中(如果链表节点大于8个,会进行链表转树,之后的插入算法就变成了树的插入算法)。

每次put之后,会检测一下是否需要扩容,size超过了 总容量 * 负载因子,则会扩容。默认情况下,16 * 0.75 = 12个。

1、为什么初始容量是16

当容量为2的幂时,上述n -1 对应的二进制数全为1,这样才能保证它和key的hashcode做&运算后,能够均匀分布,这样才能减少hash碰撞的次数。至于默认值为什么是16,而不是2 、4、8,或者32、64、1024等,我想应该就是个折中处理,过小会导致放不下几个元素,就要进行扩容了,而扩容是一个很消耗性能的操作。取值过大的话,无疑会浪费更多的内存空间。因此在日常开发中,如果可以预估HashMap会存入节点的数量,则应该在初始化时,指定其容量。

2、为什么负载因子是0.75

也是一个综合考虑,如果设置过小,HashMap每put少量的数据,都要进行一次扩容,而扩容操作会消耗大量的性能。如果设置过大的话,如果设成1,容量还是16,假设现在数组上已经占用的15个,再要put数据进来,计算数组index时,发生hash碰撞的概率将达到15/16,这违背的HashMap减少hash碰撞的原则。

以上这篇谈谈Hashmap的容量为什么是2的幂次问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持猪先飞。

[!--infotagslink--]

相关文章

  • java中JSONObject转换为HashMap(方法+main方法调用实例)

    这篇文章主要介绍了java中JSONObject转换为HashMap(方法+main方法调用实例),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-14
  • hashMap扩容时应该注意这些死循环问题

    今天给大家带来的是关于Java的相关知识,文章围绕着hashMap扩容时的死循环问题展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下...2021-06-10
  • Mysql 查询数据库容量大小的方法步骤

    这篇文章主要介绍了Mysql 查询数据库容量大小的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-09
  • Java中HashMap集合的常用方法详解

    本篇文章给大家带来的内容是关于Java中HashMap集合的常用方法详解,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。下面我们就来学习一下吧...2021-11-04
  • Java那点儿事之Map集合不为人知的秘密有哪些

    Map用于保存具有映射关系的数据,Map集合里保存着两组值,一组用于保存Map的key,另一组保存着Map的value,和查字典类似,通过key找到对应的value,通过页数找到对应的信息。用学生类来说,key相当于学号,value对应name,age,sex等信息。用这种对应关系方便查找...2021-10-21
  • 解析ConcurrentHashMap: 红黑树的代理类(TreeBin)

    ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment的结构和HashMap类似,是一种数组和链表结构,今天给大家普及java面试常见问题---ConcurrentHashMap知识,一起看看吧...2021-06-11
  • Java中HashMap里面key为null存放到哪

    这篇文章主要介绍了Java中HashMap里面key为null存放到哪,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-04
  • 深入理解Java中的HashMap

    HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。随着JDK(Java Developmet Kit)版本的更新,JDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等。本文将深入探讨HashMap的结构实现和功能原理...2021-06-11
  • Java中ConcurrentHashMap是如何实现线程安全

    ConcurrentHashMap是一个哈希表,支持检索的全并发和更新的高预期并发。本文主要介绍了Java中ConcurrentHashMap是如何实现线程安全,感兴趣的可以了解一下...2021-11-03
  • HashMap在JDK7与JDK8中的实现过程解析

    这几天学习了HashMap的底层实现,但是发现好几个版本的,代码不一,很多文章都是旧版本JDK1.6.JDK1.7的。现在我来分析下JDK7与JDK8中HashMap的实现过程...2021-09-18
  • java中Hashmap的get方法使用

    这篇文章主要介绍了java中Hashmap的get方法使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-13
  • Java中使用HashMap改进查找性能的步骤

    这篇文章主要介绍了Java中使用HashMap改进查找性能的步骤,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下...2021-02-07
  • 基于hashmap 的扩容和树形化全面分析

    这篇文章主要介绍了hashmap 的扩容和树形化的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-11
  • Java面试题之HashMap 的 hash 方法原理是什么

    那天,小二去蔚来面试,面试官老王一上来就问他:HashMap 的 hash 方法的原理是什么?当时就把裸面的小二给蚌埠住了,这篇文章将详细解答该题目...2021-11-05
  • 谈谈Hashmap的容量为什么是2的幂次问题

    这篇文章主要介绍了谈谈Hashmap的容量为什么是2的幂次问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-24
  • Java多线程高并发中解决ArrayList与HashSet和HashMap不安全的方案

    ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步,HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,关于HashSet有一件事应该牢记,即就条目数和容量之和来讲,迭代是线性的,接下来让我们详细来了解吧...2021-11-16
  • JAVA中哈希表HashMap的深入学习

    这篇文章主要介绍了哈希表HashMap的深入学习,哈希表是一种非常重要的数据结构,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,本文会对java集合框架中HashMap的实现原理进行讲解。感兴趣的话可以一起来学习...2020-07-13
  • java编程进阶小白也能手写HashMap代码

    这篇文章是一篇java小白进阶篇本文教大家手写一个HashMap实现的示例代码,有需要的朋友可以借鉴参考下,希望对大家能够有所进益,祝大家早日升职加薪...2021-10-15
  • HashMap原理及put方法与get方法的调用过程

    这篇文章主要介绍了HashMap原理及put方法与get方法的调用过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-13
  • 解析ConcurrentHashMap: get、remove方法分析

    ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment的结构和HashMap类似,是一种数组和链表结构,今天给大家普及java面试常见问题---ConcurrentHashMap知识,一起看看吧...2021-06-11