前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >探究HashMap源码中最大容量为什么是2的30次方(1<<30)?

探究HashMap源码中最大容量为什么是2的30次方(1<<30)?

作者头像
向着百万年薪努力的小赵
发布2022-12-02 10:42:10
4200
发布2022-12-02 10:42:10
举报
文章被收录于专栏:小赵的Java学习

关于HashMap的详解文章请移步:

链接: HashMap源码研究——源码一行一行的注释

文章目录

在阅读hashmap的源码过程中,我看到了关于hashmap最大容量的限制,并产生了一丝疑问。

代码语言:javascript
复制
	/**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

为啥最大容量是 1 << 30?超过会怎么办? 有个面试题: HashMap最大容量是多少? 如果你答2的30次方,面试官极有可能追问: 真的吗? 假的!这是默认的最大阈值容量,实际上是什么样? 让我们往下看

为什么是30

首先是 << 这个操作符必须要理解 在一般情况下 1 << x 等于 2^x。这是左移操作符,对二进制进行左移。 右移正好相反,换句话说就是右移缩小两倍,左移扩大两倍 来看1 << 30。它代表将1左移30位,也就是0010…0

  • int类型是32位整型,占4个字节。
  • Java的原始类型里没有无符号类型。 -->所以首位是符号位 正数为0,负数为1
  • java中存放的是补码,1左移31位的为 16进制的0x80000000代表的是-2147483648–>所以最大只能是30

为什么是1<<30

那为什么是 1 << 30,而不是0x7fffffffInteger.MAX_VALUE

代码语言:javascript
复制
	/**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

翻译一下大概就是:如果构造函数传入的值大于该数 ,那么替换成该数。 就是初始化的时候,如果容量超过这个数,就替换成该数 我们看看构造函数的调用:

代码语言:javascript
复制
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)   //如果初始容量小于0,抛出异常
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)  //如果初始容量超过最大容量(1<<32)
            initialCapacity = MAXIMUM_CAPACITY;  //则使用最大容量作为初始容量
        if (loadFactor <= 0 || Float.isNaN(loadFactor))  //如果负载因子小于等于0或者不是数字,则抛出异常
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
        this.loadFactor = loadFactor;                //把负载因子赋值给成员变量loadFactor

//调用tableSizeFor方法计算出不小于initialCapacity的最小的2的幂的结果,并赋给成员变量threshold
        this.threshold = tableSizeFor(initialCapacity); 
    }

其中这一句:

代码语言:javascript
复制
if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;

如果我要存的数目大于 MAXIMUM_CAPACITY,你还把我的容量缩小成 MAXIMUM_CAPACITY?

别急,继续看:在扩容resize()方法中有一句:

代码语言:javascript
复制
if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
}

在这里我们可以看到其实 hashmap的“最大容量“是Integer.MAX_VALUE,即int的最大值 上面那个面试题知道怎么说了吗,那是默认最大值,如果超过,最大容量其实是int的最大值

为什么容量总能是2的次幂

MAXIMUM_CAPACITY作为一个2的幂方中最大值,这个值的作用涉及的比较广。其中有一点比较重要的是在hashmap中容量会确保是 2的k次方,即使你传入的初始容量不是 2的k次方,tableSizeFor()方法也会将你的容量置为 2的k次方。这时候MAX_VALUE就代表了最大的容量值。

代码语言:javascript
复制
/**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;      //容量减1,为了防止初始化容量已经是2的幂的情况,最后有+1运算。
        n |= n >>> 1;         //将n无符号右移一位再与n做或操作
        n |= n >>> 2;         //将n无符号右移两位再与n做或操作
        n |= n >>> 4;         //将n无符号右移四位再与n做或操作
        n |= n >>> 8;         //将n无符号右移八位再与n做或操作
        n |= n >>> 16;        //将n无符号右移十六位再与n做或操作
        //如果入参cap为小于或等于0的数,那么经过cap-1之后n为负数,n经过无符号右移和或操作后仍未负 
        //数,所以如果n<0,则返回1;如果n大于或等于最大容量,则返回最大容量;否则返回n+1
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

这个方法就是获取离你容量最近的一个2的次幂

threshold阈值

另外还有一点就是threshold,如果对hashmap有一点了解的人都会知道threshold = 初始容量 * 加载因子。也就是扩容的 门槛。相当于实际使用的容量。而扩容都是翻倍的扩容。那么当容量到达MAXIMUM_CAPACITY,这时候再扩容就是 1 << 31 整型溢出。所以Integer.MAX_VALUE作为最终的容量,但是是一个threshold的身份。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 为什么是30
  • 为什么是1<<30
  • 为什么容量总能是2的次幂
  • threshold阈值
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档