zoukankan      html  css  js  c++  java
  • HashMap的默认容量为什么要设置16?

    在HashMap中,indexFor方法其实主要是将hashcode换成链表数组中的下标。

    static int indexFor(int h, int length) {
        return h & (length-1);
    }

    这里实际就是取模。

    用位运算是因为它比取模运算效率要高很多,因为它是直接对内存数据操作,不需要转成十进制,因此处理速度非常快。

    但是需要length是2^n, 这样才满足:

    X % 2^n = X & (2^n – 1)

    所以,HashMap的容量一定要是2^n。

    那么为什么要是16呢?而不是4,8 ,32呢?

    这应该是经验值,需要在效率和内存使用上做一个权衡。这个值不能太大,也不能太小。

    太小了就可能会频繁的发生扩容,影响效率;太大了又浪费空间,不划算。

    所以,16作为一个经验值就被采用了。

    那么HashMap如何保证其容量一定可以是2^n呢?

    HashMap在两个可能改变其容量的地方都做了兼容处理:

      1. 指定容量初始化值时;

      2. 扩容时;

    指定容量初始化值时

    HashMap根据用户传入的初始化容量,利用无符号右移和按位或运算等方式计算出第一个大于该数的2的幂。

    看一下JDK是如何找到比传入的指定值大的第一个2的幂的:

    int n = cap - 1;
    //step1 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;//step2

    上面的算法目的挺简单,就是:根据用户传入的容量值(代码中的cap),通过计算,得到第一个比他大的2的幂并返回。

    step1具体 怎么理解呢?其实是对一个二进制数依次向右移位,然后与原值取或。其目的对于一个数字的二进制,从第一个不为0的位开始,把后面的所有位都设置成1。随便拿一个二进制数,套一遍上面的公式就发现其目的了:

    1100 1100 1100 >>>1 = 0110 0110 0110
    1100 1100 1100 | 0110 0110 0110 = 1110 1110 1110
    1110 1110 1110 >>>2 = 0011 1011 1011
    1110 1110 1110 | 0011 1011 1011 = 1111 1111 1111
    1111 1111 1111 >>>4 = 1111 1111 1111
    1111 1111 1111 | 1111 1111 1111 = 1111 1111 1111

    Step 2 比较简单,就是做一下极限值的判断,然后把Step 1得到的数值+1。

    另外注意:

    在JDK 1.7和JDK 1.8中,HashMap初始化这个容量的时机不同

    JDK 1.8中,在调用HashMap的构造函数定义HashMap的时候,就会进行容量的设定。

    而在JDK 1.7中,要等到第一次put操作时才进行这一操作。

    总之,HashMap根据用户传入的初始化容量,利用无符号右移和按位或运算等方式计算出第一个大于该数的2的幂

    扩容

    除了初始化的时候会指定HashMap的容量,在进行扩容的时候,其容量也可能会改变。

    HashMap有扩容机制,就是当达到扩容条件时会进行扩容。

    HashMap的扩容条件就是当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容。

    在HashMap中,threshold = loadFactor * capacity。

    loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f,设置成0.75有一个好处,那就是0.75正好是3/4,而capacity又是2的幂。

    所以,两个数的乘积都是整数。

    下面是HashMap中的扩容方法(resize)中的一段:

    if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
        oldCap >= DEFAULT_INITIAL_CAPACITY)
        newThr = oldThr << 1; // double threshold
    }

    从上面代码可以看出,扩容后的table大小变为原来的两倍,这一步执行之后,就会进行扩容后table的调整,这部分非本文重点,省略。

    所以,通过保证初始化容量均为2的幂,并且扩容时也是扩容到之前容量的2倍,所以,保证了HashMap的容量永远都是2的幂。

    总结

    HashMap作为一种数据结构,元素在put的过程中需要进行hash运算,目的是计算出该元素存放在hashMap中的具体位置。

    hash运算的过程其实就是对目标元素的Key进行hashcode,再对Map的容量进行取模,而JDK 的工程师为了提升取模的效率,使用位运算代替了取模运算,这就要求Map的容量一定得是2的幂。

    而作为默认容量,太大和太小都不合适,所以16就作为一个比较合适的经验值被采用了。

    为了保证任何情况下Map的容量都是2的幂,HashMap在两个地方都做了限制。

    首先是,如果用户制定了初始容量,那么HashMap会计算出比该数大的第一个2的幂作为初始容量。

    另外,在扩容的时候,也是进行成倍的扩容,即4变成8,8变成16。

    参考:https://mp.weixin.qq.com/s/ktre8-C-cP_2HZxVW5fomQ

  • 相关阅读:
    集群、分布式、负载均衡区别与联系
    Android--加载大分辨率图片到内存
    Android--MP3播放器MediaPlayer
    Android--SoundPool
    Android--MediaPlayer高级
    Android--调用系统照相机拍照与摄像
    Android--SurfaceView播放视频
    Android--使用VideoView播放视频
    Android--使用Camera拍照
    Vue组件选项props
  • 原文地址:https://www.cnblogs.com/Vincent-yuan/p/15178149.html
Copyright © 2011-2022 走看看