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/zh-hant/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

發表回復

登錄後才能評論