HashMap为什么是2的幂次方

一、HashMap为什么是2的幂次方

HashMap 是 Java 集合体系中的一个底层实现工具类,采用散列表实现,因此其底层采用数组存储。数组是一种随机访问结构,可以通过固定偏移量和下标的计算方式直接跳转到指定位置。但是,散列表可以使元素在数组中的位置任意,所以必须通过散列函数计算元素所在位置,如果不重写散列函数,Java 使用的是 hashCode() 方法的返回值,而 hashCode() 返回一个整数,所以 HashMap 底层的数组必须是整数下标。

散列表按照键值对存储,也就是说要存储两个值:键和值,这里我们称之为 entry。这些 entry 存储在一个 Entry 数组中,在 Java 8 中,该数组被 Node 数组取代,每个 Node 保存一个键值对。数组的长度是固定的,而且在初始化时必须得到指定,但是作为编程人员,在写程序时很难事先预知将要存储到 Map 中的键值对的数量,因此,Java 在该初始化确定数组长度后,如果达到一定条件时,自动对该数组进行扩展,进而改变数组长度。

在 HashMap 中,数组长度是2的幂次方,这是因为计算机在计算 hash 值时就需要用到 “& size – 1″,而计算机在做除法时除法的成本远高于求余数的成本,同时采用位运算的效率通常要比模运算的效率高,因此使用2的幂次方作为数组长度,是因为形式上最能体现出求余数和位运算的最佳实践。

    /**
     * 初始化容量(必须是 2 的幂且小于 1 << 30)
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

二、HashMap扩容为什么是2倍

扩容的本质是数组扩容,数组的长度必须是2的幂次方,如果按1进行扩容数组长度的画,每次扩容都要重新计算每个元素的位置,而2倍扩容操作是一次性完成,是为了加速计算,同时也为了防止出现哈希冲突,保证散列的均匀性。

    /**
     * 加载因子,当 HashMap 所容纳的元素数量达到 totalSize * loadFactor 时,就需扩容
     */
    final float loadFactor;

    /**
     * 扩容操作,扩容后的大小必须是2的幂次方
     */
    final void resize() {
        //...
        newCap = oldCap << 1;
        //...
    }

三、HashMap的长度为什么是2的幂次方

HashMap 的长度并不是随意设置,而是必须是2的幂次方,这样一来,可以使Entry放入数组时,并且计算出它的位置 index。使用 n & (length-1) 就相当于对 length 取模,但是比直接对 length 取模效率更高,因为&运算比%运算效率更高。

在 JDK1.7 中,HashMap 有一个默认的负载因子 loadFactor,如果散列表中当前元素个数超过了数组长度与负载因子的积,就需要进行扩容,否则,当hash表中的元素个数过多,会出现大量的hash冲突,会导致HashMap变慢。JDK1.8 对扩容机制做出了调整,基本上渐进时间复杂度从 O(n) 降低到了 O(1)。

    /**
     * 初始化容量(必须是 2 的幂且小于 1 << 30)
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

    /**
     * 最大容量(必须是 2 的幂且小于 1 << 30)
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 链表的最小长度
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /**
     * 链表长度过长时,转换成红黑树
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 当转化红黑树后,最小的hash表容量大小
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 加载因子,当 HashMap 所容纳的元素数量达到 totalSize * loadFactor 时,就需扩容
     */
    final float loadFactor;

四、HashMap为什么链表选取3~5个

为了解决哈希冲突,Java 在 JDK1.8 中通过弱化了 Node 结构中的 next 指针,在链表长度等于8时才考虑红黑树化后的转换。链表转换成红黑树,只针对 HashMap,ConcurrentHashMap 在 JDK1.8中并没有这种转化逻辑。由于红黑树的高度不高于 log(n),相当于在链表长度大于8时,查找效率更高一些。

    /**
     * 当转化红黑树后,最小的hash表容量大小
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 链表长度过长时,转换成红黑树
     */
    static final int TREEIFY_THRESHOLD = 8;

通过以上分析,可以看出 HashMap 选择 2 的幂次方作为数组长度的原因,HashMap 长度为什么是2的幂次方,扩容为什么是2倍,以及链表选取3~5个的相关原因。这些策略使得 HashMap 在底层实现中保证了时间和空间的效率。

原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/186694.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
小蓝的头像小蓝
上一篇 2024-11-27 05:47
下一篇 2024-11-27 05:48

相关推荐

  • Python2的N次方

    在Python2中,求n次方可以使用Python内置的乘法运算符(*)来实现。具体的使用方法以及相关的细节问题,可以从以下几个方面进行阐述。 一、方法1:使用“**”运算符 方法1…

    编程 2025-04-29
  • 2的32次方-1:一个看似简单却又复杂的数字

    对于计算机领域的人来说,2的32次方-1(也就是十进制下的4294967295)这个数字并不陌生。它经常被用来表示IPv4地址或者无符号32位整数的最大值。但实际上,这个数字却包含…

    编程 2025-04-27
  • 二分查找时间复杂度为什么是logN – 知乎

    二分查找是一种常用的查找算法。它通过将目标值与数组的中间元素进行比较,从而将查找范围缩小一半,直到找到目标值。这种方法的时间复杂度为O(logN)。下面我们将从多个方面探讨为什么二…

    编程 2025-04-27
  • Python输出2的n次方

    Python是一种强大的编程语言,拥有丰富的语法结构和内置函数。在Python中,输出2的n次方是一项常见任务,因为它可以帮助我们解决很多实际问题。本文将从多个方面详细介绍Pyth…

    编程 2025-04-27
  • Python次方怎么打

    一、使用乘号实现次方 在Python中,可以使用乘号“*”来实现次方运算。例如,2的3次方可以表示为: 2 ** 3 其中,“**”就表示次方运算。这种方法适用于底数和指数都是整数…

    编程 2025-04-23
  • Python次方运算符

    一、基本介绍 Python中的次方运算符是 **,它用于计算幂运算。例如: x = 2 y = 3 print(x ** y) 输出结果为: 8 这表示2的3次方等于8。 次方运算…

    编程 2025-03-12
  • e的x次方的平方探究

    一、e的x次方的平方是什么 在数学中,e的x次方的平方,也可以写作(e的x)^2,实际上是e^(2x),其中e代表自然对数的底数,x代表指数。当x为任意实数时,e的x次方的平方是一…

    编程 2025-02-25
  • 深度解析hashmap负载因子

    hashmap是一个非常常见的数据结构之一,它具有快速的查找和插入操作。负载因子是hashmap中非常重要的一个概念,本文将从多个方面深度解析hashmap负载因子的含义、计算方法…

    编程 2025-02-25
  • Java中HashMap的遍历方法

    一、基本介绍 HashMap是Java中十分常用的一种数据结构,在开发和实际应用中也频繁使用。HashMap是一种基于哈希表的Map接口实现,它允许null值和null键,同时它也…

    编程 2025-02-24
  • Redis端口为什么是6379

    一、Redis概述 Redis是一个开源的高性能的Key-Value(键值对)内存数据库,致力于为互联网应用提供快速、可扩展、可靠的数据存储服务。Redis支持多种数据结构:字符串…

    编程 2025-02-24

发表回复

登录后才能评论