day24--Java集合07

来源:https://www.cnblogs.com/liyuelian/archive/2022/08/23/16617881.html
-Advertisement-
Play Games

Java集合07 14.HashMap底層機制 (k,v)是一個Node,實現了Map.Entry<K,V>,查看HashMap的源碼可以看到 jdk7.0 的HashMap底層實現[數組+鏈表],jdk8.0底層[數組+鏈表+紅黑樹] 14.1HashMap擴容機制(和HashSet完全相同) 詳 ...


Java集合07

14.HashMap底層機制

hashMap底層機制08201904

  1. (k,v)是一個Node,實現了Map.Entry<K,V>,查看HashMap的源碼可以看到
  2. jdk7.0 的HashMap底層實現[數組+鏈表],jdk8.0底層[數組+鏈表+紅黑樹]

14.1HashMap擴容機制(和HashSet完全相同)

詳見10.2HashSet的底層擴容機制

  1. HashMap底層維護了Node類型的數組table,預設為null
  2. 當創建對象時,將載入因數(loadfactor)初始化為0.75
  3. 當添加key-value時,通過key的哈希值得到在table的索引。然後判斷該索引處是否有元素,如果沒有元素則直接添加;如果該索引處有元素,繼續判斷該元素的key是否和準備加入的key相等。若相等,則直接替換value;若不相等,需要判斷是樹結構還是鏈表結構,作出相應處理。如果添加是發現容量還不夠,則需要擴容。
  4. 第一次添加,則需要擴容table容量為16,臨界值(threshold)為(0.75*16=)12
  5. 以後再擴容,則需要擴容為table的容量為之前的兩倍,臨界值也為原來的兩倍,即24.以此類推
  6. 在Java8中,如果一條鏈表的元素個數超過TREEIFY_THRESHOLD(預設為8),並且table的大小>=MIN_TREEIFY_CAPACITY(預設64),就會進行樹化(紅黑樹)。

例子:

package li.map.hashmap;

import java.util.HashMap;

@SuppressWarnings("all")
public class HashMapSource {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("java",10);//ok
        map.put("php",10);//ok
        map.put("java",20);//替換value

        System.out.println(map);//{java=20, php=10}
    }
}

執行過程如下:

  1. 執行構造器 newHashMap();

    初始化載入因數 loadfactor = 0.75

    HashMap$Node[ ] = null

  2. 執行put(),調用hash()方法計算key的值

    可以看到,如果傳入的參數key為空的話,就返回0;如果不為空,則求出 key 的 hashCode 值,然後將hashCode 值右移16位並且與原來的 hashCode 值進行 ^(按位異或) 操作,並返回這個哈希值

public V put(K key, V value) {//K="java" value= 10
    return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

3.調用putVal方法:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {//
    Node<K,V>[] tab; Node<K,V> p; int n, i;//定義了輔助變數
    //這裡定義的tablejiushi HashMap的一個數組,類型是Node[]數組
    if ((tab = table) == null || (n = tab.length) == 0)//if 語句表示,如果當前table是null,或者大小=0,則進行第一次擴容,擴容到16個空間
        n = (tab = resize()).length;//如果為第一次擴容,此時初始的table已經變成容量為16的數組
    
    /*
    1.根據key,得到hash 去計算key應該放到table表的哪個索引位置,並且把這個未知的對象賦給賦值變數p  	     2.再判斷p是否為空 
    	2.1如果p為空,表示該位置還沒存放元素,就創建一個Node (key="java", value=PRESENT)並把數         據放在該位置--table[i]=newNode(hash, key, value, null);
    */
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    
    
    else {
      //2.2如果不為空,就會進入else語句
        Node<K,V> e; K k;//定義輔助變數
       /*這裡的p指向當前索引所在的對象(由上面的p = tab[i = (n - 1) & hash])計算出索引位置),如          果當前索引位置對應鏈表的第一個元素的哈希值 和 準備添加的key的哈希值 一樣,
       並且 滿足下麵兩個條件之一:
        1.準備加入的key 和 p指向的Node節點 的key 是同一個對象:(k = p.key) == key
        2.p指向的Node節點的key 的equals()和準備加入的key比較後相同 並且key不等於null:(key !=               null && key.equals(k))
       就不加入  只是換原來的元素(不插入新結點只是替換值)
        */
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        
        //再判斷p是否是一顆紅黑樹
        //如果是紅黑樹,就調用putTreeVal()方法來進行添加
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        
        else { //如果table對應索引位置已經是一個鏈表了,就使用for迴圈依次比較
               //(1)依次和該鏈表的每個元素都比較後 都不相同,就則將數據加入到該鏈表的最後
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {//先賦值再判斷
                    p.next = newNode(hash, key, value, null);
                    //註意:把元素添加到鏈表之後立即 判斷該鏈表是否已經達到8個節點,如果已經達到則                         //調用 treeifyBin()對當前鏈表進行樹化(轉成紅黑樹)
                    //在轉成紅黑樹時 還要進行一個判斷:
                    //如果該table數組的為空或者大小小於64,則對table數組進行擴容
                    //如果上麵條件不成立,即數組大小大於等於64且鏈表數量達到8個,就轉成紅黑樹
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                
                 //(2)如果在依次和該鏈表的每個元素比較的過程中發現如果有相同情況,就直接break
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;//在上面for迴圈條件已經把p.next賦值給e了,這裡e又賦值給p 其實就是將p指針指                         //向p.next,然後再進行新一輪的判斷,如此迴圈,直到有滿足上面if語句的條件為止
            }
     }
        
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;//替換,key對應value
            afterNodeAccess(e);
            return oldValue;
        }
    }
    
    
    ++modCount;//每增加一個Node,就size++
    if (++size > threshold)//當使用的容量 > 臨界值時,就擴容
        resize();
    afterNodeInsertion(evict);
    return null;
}

PS:關於樹化

        for (int binCount = 0; ; ++binCount) {
            //(1)依次和該鏈表的每個元素都比較後 都不相同,就則將數據加入到該鏈表的最後
                if ((e = p.next) == null) {//先賦值再判斷
                    p.next = newNode(hash, key, value, null);
                    
                    //註意:把元素添加到鏈表之後立即 判斷該鏈表是否已經達到8個節點,如果已經達到則                         //調用 treeifyBin()對當前鏈表進行樹化(轉成紅黑樹)
                    //在轉成紅黑樹時 還要進行一個判斷:
                    //如果該table數組的為空或者大小小於64,則對table數組進行擴容
                    //如果上麵條件不成立,即數組大小大於等於64且鏈表數量達到8個,就轉成紅黑樹
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                
                 //(2)如果在依次和該鏈表的每個元素比較的過程中發現如果有相同情況,就直接break
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;//在上面for迴圈條件已經把p.next賦值給e了,這裡e又賦值給p 其實就是將p指針指                         //向p.next,然後再進行新一輪的判斷,如此迴圈,直到有滿足上面if語句的條件為止
            }

遍歷過程中p從第一個節點遍歷到最後一個節點,但由於binCount是從0開始計數,所以在做樹化判斷時binCount

的值等於 鏈表長度 - 1(註意此時的鏈表長度沒有算新插入的節點)

判斷條件為 binCount >= TREEIFY_THRESHOLD - 1 ==> binCount+1(鏈表長度) >= TREEIFY_THRESHOLD

但此時鏈表新插入了一個節點

 p.next = newNode(hash, key, value, null);

所以鏈表樹化的那一刻,它的真實長度應該時binCount+1+1 => 鏈表長度>TREEIFY_THRESHOLD(8)

即:

鏈表長度大於8時,treeifyBin()方法被調用

(在做樹化判斷時,鏈表長度 = binCount+1(從零計數)+1(新插入節點) = bincount +2)

(判斷條件: (bincount >= 8-1) => (bincount>=7) => (bincount+2>=9) => (鏈表長度>=9) 長度是整數 大於等於9也就是大於8)

ps:剪枝--->如果有鏈表樹化之後,樹中的節點經過刪除之後越來越少,當元素個數減少到一定程度,樹會轉變為了鏈表


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

-Advertisement-
Play Games
更多相關文章
  • 1. MyBatis數據輸入 1.1 Mybatis總體機制概括 1.2 概念說明 註意:這裡的簡單類型不是指的基本數據類型。 1.3 單個簡單類型參數 1.3.1 Mapper介面中的抽象方法 public interface EmpMapper { /** * 通過這個方法對應Mapper配置文 ...
  • 服務一個人的系統,和服務一億人的系統,複雜度有著天壤之別。本文從工程師文化、組織戰略、公司內部協作等角度來分析軟體複雜度形成的原因,並提出了一些切實可落地的解法。 ...
  • 一、問題的描述 在實際的系統應用開發中我經常會遇到這樣的一類需求,相信大家在工作中也會經常遇到: 同一個系統在多個省份部署。 一個業務在北京是一種實現方式,是基於北京用戶的需求。 同樣的業務在上海是另外一種實現方式,與北京的實現方式大同小異 遇到這樣的需求,我們通常會定義一個業務實現的介面,比如: ...
  • 1 讓自己習慣C++ 條款 01 視 C++ 為一個語言聯邦 C : C++以C為基礎,block、語句、預處理器、內置數據類型、數組、指針都來自於C。當使用C++中的C成分工作時,沒有模板(Template)、沒有異常(Exceptions)、沒有重載(overloading)。 Object-O ...
  • 面試手撕併發演算法題 固定列印順序 使用 wait-notify 實現以下功能:先列印 b,再列印 a 思路一 線程t1和t2同時運行,t1中列印 a,t2中列印 b,但 t1 列印得有個前提,就是 t1要在t2運行完釋放鎖了才能列印 a。如果t1先得到鎖,但t2沒有執行,還是得釋放鎖,讓t2得到鎖先 ...
  • 目錄 一.OpenGL ES 波浪特效效果演示 1.原始圖片 2.效果演示 二.OpenGL ES 波浪特效源碼下載 三.猜你喜歡 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL ES 學習路線推薦 : OpenGL ...
  • 系列文章目錄 第一章:武裝飛船 07調整飛船速度 08限制飛船活動範圍 一、代碼及演示 1.修改settings 修改文件:settings.py 點擊查看代碼 #滲透小紅帽python的學習之路 #外星人入侵小游戲 #創建設置類Setting() #存儲外星人入侵小游戲的所有設置的類 class ...
  • 用戶線程和守護線程瞭解嗎? 什麼是用戶線程和守護線程? 守護線程是一種特殊的線程,在後臺默默地完成一些系統性的服務,比如垃圾回收線程、JIT線程都是守護線程。與之對應的是用戶線程,用戶線程可以理解為是系統的工作線程,它會完成這個程式需要完成的業務操作。 如何手動設置線程為守護線程? java 中的線 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...