HashMap實現原理學習

来源:https://www.cnblogs.com/xiaxveliang/archive/2020/03/02/12396081.html
-Advertisement-
Play Games

HashMap源碼來自:android 25/java/util/HashMap 一、構造方法 下麵通過跟中源碼查看: table數組初始化 介紹put(K key, V value)方法前,先簡單介紹table數組初始化 ps: 這裡預設初始化了一個數組容量為16的table數組,其中關於roun ...


HashMap源碼來自:android-25/java/util/HashMap

一、構造方法

static final int MAXIMUM_CAPACITY = 1 << 30;
static final int DEFAULT_INITIAL_CAPACITY = 4;
static final float DEFAULT_LOAD_FACTOR = 0.75f;

public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
// 參數預設為 4,0.75f
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        // 4 < MAXIMUM_CAPACITY
        if (initialCapacity > MAXIMUM_CAPACITY) {
            initialCapacity = MAXIMUM_CAPACITY;
        }
        // 4 = DEFAULT_INITIAL_CAPACITY
        else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {
            initialCapacity = DEFAULT_INITIAL_CAPACITY;
        }
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        threshold = initialCapacity;
        init();
}

ps:
init()為空方法;構造方法中只是做了HashMap數組容量欄位的一個簡單限制,最大為MAXIMUM_CAPACITY,最小為DEFAULT_INITIAL_CAPACITY

二、添加元素 put(K key, V value)

添加數據時,若出現衝突。
Java是通過 數組+鏈表 的形式解決衝突。效果如下圖所示:
這裡寫圖片描述

  • HashMap中有一個預設長度為16的table數組,當數組的容量達到預設長度的0.75倍時,則擴容兩倍;
  • 其中table數組的每一項數據結構如下:
static class HashMapEntry<K,V> implements Map.Entry<K,V> {
    final K key; // key
    V value;     // value
    HashMapEntry<K,V> next; // 鏈表的下一項
    int hash;    // key 的hash值
}

下麵通過跟中源碼查看:

table數組初始化

介紹put(K key, V value)方法前,先簡單介紹table數組初始化

// 添加key value
public V put(K key, V value) {
    // 如果table列表為null,則用過inflateTable方法初始化
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    ...
    return null;
}
// 初始化table數組
private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        // 這裡計算一個2的n次方的數組容量,預設為2的4次方,為16
        int capacity = roundUpToPowerOf2(toSize);
        // 計算數組容量的0.75倍,超過數組容量0.75倍時,數組需要擴容
        float thresholdFloat = capacity * loadFactor;
        if (thresholdFloat > MAXIMUM_CAPACITY + 1) {
            thresholdFloat = MAXIMUM_CAPACITY + 1;
        }
        // 數組容量的0.75倍
        threshold = (int) thresholdFloat;
        // 初始化數組,預設容量capacity為16
        table = new HashMapEntry[capacity];
}

ps:
這裡預設初始化了一個數組容量為16的table數組,其中關於roundUpToPowerOf2(toSize)為什麼為2的n次方的問題,在下邊進行介紹

put(K key, V value)

// 添加key value
public V put(K key, V value) {
        // 如果table列表為null,則用過inflateTable方法初始化
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        // key 為null,則添加key為null的value
        if (key == null)
            return putForNullKey(value);
        // 根據key獲取hash值
        // Jenkins hash演算法,可參考以下鏈接:
        // https://en.wikipedia.org/wiki/Jenkins_hash_function
        int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
        //  h & (length-1) 取餘太消耗性能,這裡通過位運算達到同樣的效果
        // 獲取該key在table 數組的index
        int i = indexFor(hash, table.length);
        // 迴圈table[i]對應的鏈表
        // 如果 hash值相同 && key相同,則替換對應value,並返回老的value值
        // 註:這裡只是迴圈table[i]位置的鏈表,對於table數組未做迴圈
        for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            // 如果 hash值相同 && key相同,則替換對應value,並返回老的value值
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        // 以下兩種情況,則需要通過createEntry方法來看了
        // hash相同 && key不同
        // hash不同 && key不同 
        addEntry(hash, key, value, i);
        return null;
}

ps:
以上介紹了添加數據時,“如果 hash值相同 && key相同,則替換對應value,並返回老的value值”,但對於“hash相同 && key不同”與“hash不同 && key不同”情況,則需要在createEntry中進行說明


void addEntry(int hash, K key, V value, int bucketIndex) {
        // 當數組的占用量,達到數組長度的0.75倍時,則需要擴容,擴展後的容量為原容量的2倍
        // 數組擴容首先創建一個長度為原數組兩倍的數組,然後將老的數組數據賦值給新數組的對應項項目
        // 數組擴容的代碼,這裡不再說明
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        // hash相同 && key不同
        // hash不同 && key不同 
        createEntry(hash, key, value, bucketIndex);
}
// hash相同 && key不同
// hash不同 && key不同 
void createEntry(int hash, K key, V value, int bucketIndex) {
        // 取出table[bucketIndex]數組的原有值,可能為null,可能為HashMapEntry
        // 若為null,則直接將value放在table[bucketIndex]位置就ok了
        // 若不為null,則將新數組放到table[bucketIndex]位置,老數組放到新數據鏈表的next欄位
        // hash衝突就是這樣解決了,可以看到確實與上圖一致,為數組+鏈表的方式解決衝突
        HashMapEntry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
        size++;
}

ps:
通過createEntry方法,我們看到HashMap中通過數組+鏈表方式解決了Hash衝突,呼應了上圖

roundUpToPowerOf2(toSize)為什麼為2的n次方

打個比方:

  • 當數組長度為15時,添加數組時h & (length-1)計算成為hash&14(0x1110),那麼最後一位永遠是0,從而造成table數組中 1(0x0001),3(0x0011),5(0x0101),7(0x0111),9(0x1001),11(0x1011)等位置永遠不可以存放數據,從而造成空間浪費;
  • 更糟的是這種情況中,數組可以使用的位置比數組長度小了很多,這意味著進一步增加了碰撞的幾率,減慢了查詢的效率。

ps:
關於 roundUpToPowerOf2(toSize)為什麼為2的n次方問題,詳細可查看
http://blog.csdn.net/yyh352091626/article/details/60866689?locationNum=4&fps=1

========== THE END ==========

wx_gzh.jpg


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 疫情已經持續了好幾個月了,作為程式員滴我們也幫不上什麼忙,只有老老實實呆在家裡或者出門一定戴口罩準守一些規則,不給國家添亂。不過最近疫情開始有所扭轉,但是還是對國家經濟,對企業業務造成了很大的影響,我也被停止了實習。接下來,可能會面臨著失業,破產等等嚴肅的問題。但是我們還是需要繼續學習,提高自己的競 ...
  • Oracle體繫結構 實例: 一個操作系統只有一個 Oracle 資料庫 一個 Oracle 資料庫可以有多個 Oracle 實例(通常只安裝一個實例) 一個實例對應著一系列的後臺進程和記憶體結構 表空間: 一個實例在邏輯上可以分成若幹個表空間 表空間是 Oracle 對數據文件的邏輯映射 表空間不屬 ...
  • Socket通信有兩種主要方式:TCP協議和UDP協議,兩者區別是TCP協議要首先和接收方要建立連接然後發送數據,這樣數據能保證送達,但速度較慢;UDP協議首先把數據打包,然後直接發送到接收方,無需建立連接誒,速度快,但容易丟失數據。這裡是一個簡單的基於TCP協議的通信實例: 直接上代碼: 首先是j ...
  • 去年計劃完成移動互聯網技術開發三部曲:微信小程式開發、iOS App開發和Android App開發的。故系列文章命名為:一個人開發一個App……開頭。 ...
  • 一、摘要 1.七牛上傳文件,用hash來唯一標識七牛存儲空間中的某個文件,該hash是以ETag演算法計算出的一段哈希值; 2.演算法介紹:https://developer.qiniu.com/kodo/manual/1231/appendix; 3.七牛的提供的實現語言中(https://githu ...
  • Android JsBridge源碼學習 眾所周知Android 4.2以下的WebView存在addJavascriptInterface漏洞的問題,不太瞭解的同學可參考 "Android4.2下 WebView的addJavascriptInterface漏洞解決方案" "@Javascript ...
  • RxJava2 使用 及 源碼閱讀 RxJava是什麼?根據RxJava在GitHub上給出的描述: RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event based pro ...
  • 今天在三星S8上遇見一個奇葩問題 一、出現場景 + 三星手機S8 android 8.0 + targetSdkVersion 27 + 透明Activity 二、解決方案 manifest中移除 三、原因(源碼中尋找) 查看Android 8.0源碼 3.1、ActivityRecord setR ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...