HashMap容量为什么是2的n次方?如何做到的?

本文围绕HashMap展开,探讨调用HashMap(9)构建map时的容量,指出其容量为16。重点分析了HashMap容量取2的n次方的原因,与hash寻址有关,&操作代替%运算需满足此条件。还介绍了HashMap通过tableSizeFor方法保证容量是2的n次方。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HashMap无疑是我们最常用的集合之一了,当我们调用HashMap(9)构建map的时候,它的容量是多少呢?

划重点!HashMap(9)构建的map的容量是16!!!这也就引出了下面的问题:

HashMap的容量为什么是2的n次方?HashMap是如何保证容量是2的n次方的?

HashMap容量取2的n次方,主要与hash寻址有关。在put(key,value)时,putVal()方法中通过i = (n - 1) & hash来计算key的散列地址。其实,i = (n - 1) & hash是一个%操作。也就是说,HashMap是通过%运算来获得key的散列地址的。但是,%运算的速度并没有&的操作速度快。而&操作能代替%运算,必须满足一定的条件,也就是a%b=a&(b-1)仅当b是2的n次方的时候方能成立。这也就是为什么HashMap的容量需要保持在2的n次方了。putVal部分代码如下:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // 此处省略了代码
        // i = (n - 1) & hash]
        if ((p = tab[i = (n - 1) & hash]) == null)
            
            tab[i] = newNode(hash, key, value, null);
        

        else {
            // 省略了代码
        }
}

那么,第二个问题“HashMap是如何保证容量是2的n次方的”,HashMap类中有个tableSizeFor方法,代码如下:

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;
}

从代码可以看出,n做了5次无符号右移(>>>)操作与或操作,那么它究竟发生了什么。其实,上述操作是将n对应的二进制数的最高位1后面全变为了1。为什么呢?看下面的例子:

hashmap容量能保持在2的n次方的原因

解释都写在上面的图上咯(不要在意字好不好看,哈哈哈~)。

码字不易,来个三连击吧~

 

推荐阅读:

这么回答面试官之--ConcurrentHashMap如何put?

这么回答面试官之--ConcurrentHashMap如何get?为何无需加锁

<think>好的,我现在需要回答用户关于ArrayDeque默认容量为什么设置为16的问题。首先,我得回忆一下Java中ArrayDeque的相关知识。记得ArrayDeque是一个基于数组的双端队列,通常用作栈或队列。它的默认容量确实是16,但具体原因呢?用户想了解设计原因及权衡分析,所以需要从多个角度考虑。 首先,可能和内存对齐有关?比如16是2的幂,这样在计算索引的时候可以用位运算来提高效率。比如,当数组长度是2的幂时,取模运算可以用更高效的位操作,比如n & (length - 1),这比直接取模要快。这可能是一个原因。比如HashMap中的容量也是使用2的幂,参考用户提供的引用[2]里提到数组长度默认是16,可能类似的设计思路。 另外,初始容量的大小需要平衡内存占用和扩容频率。默认值太小的话,频繁扩容会影响性能;太大的话又会浪费内存。16这个数在大多数情况下可能是一个折中的选择,比如对于小规模的队列操作,16足够使用,减少初始内存分配,同时避免频繁扩容。比如引用[2]中提到HashMap的默认加载因子0.75是权衡时间和空间的结果,可能ArrayDeque的初始容量也有类似的考虑。 还有,可能和计算机科学中的常见习惯有关,比如很多数据结构的初始容量使用2的幂,比如16、32等,这样在分配内存时更高效,因为计算机系统通常以2的幂来管理内存块,这可能减少内存碎片或提高分配效率。 另外,ArrayDeque在扩容的时候通常是翻倍增长,比如从16扩容到32,这样可以保证每次扩容后的容量仍然是2的幂,维持高效的索引计算。如果初始容量是16,每次翻倍后的容量仍然保持这个特性。这也符合引用[3]中提到的,数组长度做取余运算时,使用2的幂可以更好地利用散列值,减少冲突,虽然这里是针对HashMap的,但类似原理可能适用于ArrayDeque。 需要验证这些猜想是否正确。比如,查看Java官方文档或源码注释是否有说明。例如,在ArrayDeque的源码中,构造函数的注释可能会提到默认容量为16的原因。根据我的记忆,ArrayDeque的无参构造函数确实会创建一个初始容量为16的数组,当元素数量超过当前容量时,会扩容为原来的两倍。 另外,考虑实际应用场景,16作为初始容量是否合理。比如,如果用户经常使用ArrayDeque作为栈,通常压入的元素数量可能不大,16足够应对多数情况,减少扩容次数。而如果作为队列使用,可能同样适用,比如处理任务队列时初始任务数较少的情况。 不过,可能存在其他因素,比如历史原因,或者与其他类库保持一致的考虑。例如,Java中很多集合类的初始容量都是16,比如HashMap、ArrayList的默认初始容量是16吗?不对,ArrayList的默认初始容量其实是10,而HashMap的默认初始容量是16(如引用[2]所提)。所以可能不同的类有不同的设计,但ArrayDeque可能更倾向于类似HashMap的设计,因为两者都涉及高效的插入和删除操作。 总结起来,ArrayDeque默认容量16的原因可能包括: 1. 2的幂次方,便于位运算优化索引计算,提高效率。 2. 平衡初始内存占用和扩容频率,避免频繁扩容带来的性能开销。 3. 与其他集合类(如HashMap)保持设计一致性,便于开发者记忆和使用。 4. 符合常见的内存管理优化策略,如减少内存碎片。 在权衡方面,较小的初始容量节省内存但可能导致更多扩容操作,较大的初始容量则相反。选择16作为中间值,在大多数情况下能够达到较好的平衡。 现在需要将这些思考整理成结构化的回答,并引用提供的参考资料中的相关内容。例如,引用[2]中关于HashMap的默认容量扩容机制,可能和ArrayDeque有相似的设计考虑,可以引用这一点来支持论点。此外,引用[3]提到的取余运算与数组长度的关系,可能同样适用于ArrayDeque的索引计算。 最后,生成相关问题,比如关于ArrayDeque扩容策略、与其他集合类的比较,或者设计选择的影响等。</think>### ArrayDeque 默认容量 16 的设计原因及权衡分析 #### 1. **2的幂次方优化计算效率** - **位运算替代模运算**:当数组长度为$2^n$时,可以通过位运算`(index & (length - 1))`快速计算下标,这比模运算`%`效率更高[^3]。例如,HashMap的默认容量也是16,同样利用了这一特性[^2]。 - **扩容一致性**:ArrayDeque扩容容量翻倍(如16→32),保持始终为$2^n$,确保位运算的优化始终有效。 #### 2. **平衡内存占用与扩容频率** - **内存效率**:16作为初始容量,既避免了过小导致频繁扩容(例如初始为8可能需要多次翻倍),又不会因过大浪费内存。这与HashMap的负载因子设计类似,均基于空间与时间的权衡。 - **典型场景适配**:大多数场景下,双端队列的初始操作规模较小(如栈操作或任务队列),16的容量足够覆盖基础需求,减少扩容次数。 #### 3. **与Java集合框架的一致性** - **设计惯例**:Java中多个集合类(如HashMap、HashSet)默认使用16作为初始容量,保持一致性可降低开发者的认知成本[^2]。 - **历史兼容性**:早期版本的设计选择延续至今,确保代码兼容性。 #### 4. **权衡分析** - **优点**: - **高效索引计算**:位运算提升性能。 - **合理内存占用**:适应多数场景,减少初始内存浪费。 - **潜在缺点**: - **极端场景不足**:若初始操作规模远超16,可能触发多次扩容。但实际中扩容成本分摊到每次操作后,仍可保持均摊$O(1)$时间复杂度。 #### 示例代码:ArrayDeque的扩容逻辑 ```java // 简化版扩容逻辑(源码节选) private void doubleCapacity() { int newCapacity = elements.length << 1; // 容量翻倍 // ...重新分配数组并复制元素 } ``` ### 总结 ArrayDeque默认容量16的设计综合了计算效率、内存利用率及框架一致性。通过2的幂次方优化位运算,平衡初始内存与扩容频率,并沿袭Java集合类的通用设计模式。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的Coder*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值