Java - HashMap

數組和鏈表

數組:

存儲區間是連續,且占用內存嚴重,空間復雜也很大,時間復雜為O(1)

優點:是隨機讀取效率很高,原因數組是連續(隨機訪問性強,查找速度快)。

缺點:插入和刪除數據效率低,因插入數據,這個位置后面的數據在內存中要往后移的,且大小固定不易動態擴展。

鏈表:

區間離散,占用內存寬松,空間復雜度小,時間復雜度O(N)

優點:插入刪除速度快,內存利用率高,沒有大小固定,擴展靈活。

缺點:不能隨機查找,每次都是從第一個開始遍歷(查詢效率低)。


HashMap

HashMap是一個集合,鍵值對的集合,源碼中每個節點用Node<K,V>表示

Node是一個內部類,這里的key為鍵,value為值,next指向下一個元素

    static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;

HashMap的數據結構為?數組+(鏈表或紅黑樹),java7 之前是數組+鏈表 ,之后是 數組+鏈表/紅黑樹,這種結構完美的解決了數組和鏈表的問題,使得查詢和插入,刪除的效率都很高。

hashmap剛開始是左邊鏈表形態

在達到某條件后原本是左邊的鏈表形態會轉為右邊紅黑樹形態

同樣,在達到某條件后,原本轉成了右邊紅黑樹形態會轉回左邊鏈表形態

這里都畫出來是為了表示方便,左右兩種形態是不同時空下的hashmap內部形態。

?


HashMap存儲元素的過程:

HashMap<String,String> map = new HashMap<String,String>();
map.put("劉德華","張惠妹");
map.put("張學友","大S");

計算出鍵“劉德華”的hashcode,該值用來定位要將這個元素存放到數組中的什么位置.

在Object類中有一個方法:public native int hashCode();

該方法用native修飾,所以是一個本地方法,所謂本地方法就是非java代碼,這個代碼通常用c或c++寫成,在java中可以去調用它。

調用這個方法會生成一個int型的整數,我們叫它哈希碼,哈希碼和調用它的對象地址和內容有關.

通過hashcode值和數組長度取模我們可以得到元素存儲的下標。

1. 數組索引的地方是空的,這種情況很簡單,直接將元素放進去就好了。

2. 已經有元素占據了索引的位置,這種情況下我們需要判斷一下該位置的元素和當前元素是否相等,使用equals來比較。

如果使用默認的規則是比較兩個對象的地址。也就是兩者需要是同一個對象才相等,當然我們也可以重寫equals方法來實現我們自己的比較規則最常見的是通過比較屬性值來判斷是否相等。

如果兩者相等則直接覆蓋,如果不等則在原元素下面使用鏈表的結構存儲該元素

因為鏈表中元素太多的時候會影響查找效率,所以當鏈表的元素個數達到8的時候使用鏈表存儲就轉變成了使用紅黑樹存儲,原因就是紅黑樹是平衡二叉樹,在查找性能方面比鏈表要高.


HashMap中的兩個重要的參數

HashMap中有兩個重要的參數:初始容量大小和加載因子,初始容量大小是創建時給數組分配的容量大小,默認值為16,加載因子默認0.75f,用數組容量大小乘以加載因子得到一個值,一旦數組中存儲的元素個數超過該值就會調用rehash方法將數組容量增加到原來的兩倍,專業術語叫做擴容.

在做擴容的時候會生成一個新的數組,原來的所有數據需要重新計算哈希碼值重新分配到新的數組,所以擴容的操作非常消耗性能.


HashMap的put(k,v)實現

首先將k,v封裝到Node對象當中(節點)

調用K的hashCode()方法得出hash值

通過哈希表函數/哈希算法,將hash值轉換成數組的下標,下標位置上如果沒有任何元素,就把Node添加到這個位置上。

如果說下標對應的位置上有鏈表。此時,就會拿著k和鏈表上每個節點的k進行equal。如果所有的equals方法返回都是false,那么這個新的節點將被添加到鏈表的末尾。

如其中有一個equals返回了true,那么這個節點的value將會被覆蓋。

java8中put 源碼:put 中調用 putVal()方法:

1.首先判斷map中是否有數據,沒有就執行resize方法

2.如果要插入的鍵值對要存放的這個位置剛好沒有元素,那么把他封裝成Node對象,放在這個位置上即可

3.如果這個元素的key與要插入的一樣,那么就替換一下。

4.如果當前節點是TreeNode類型的數據,執行putTreeVal方法

5.遍歷這條鏈子上的數據,完成了操作后多做了一件事情,判斷,并且可能執行treeifyBin方法

static final int TREEIFY_THRESHOLD = 8;public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab;Node<K,V> p;int n, i;//如果當前map中無數據,執行resize方法。并且返回nif ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//如果要插入的鍵值對要存放的這個位置剛好沒有元素,那么把他封裝成Node對象,放在這個位置上即可if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);//否則的話,說明這上面有元素else {Node<K,V> e; K k;//如果這個元素的key與要插入的一樣,那么就替換一下。if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;//1.如果當前節點是TreeNode類型的數據,執行putTreeVal方法else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//還是遍歷這條鏈子上的數據,跟jdk7沒什么區別for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);//2.完成了操作后多做了一件事情,判斷,并且可能執行treeifyBin方法if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null) //true || --e.value = value;//3.afterNodeAccess(e);return oldValue;}}++modCount;//判斷閾值,決定是否擴容if (++size > threshold)resize();//4.afterNodeInsertion(evict);return null;}


HashMap的map.get(k)實現

先調用k的hashCode()方法得出哈希值,并通過哈希算法轉換成數組的下標

通過上一步哈希算法轉換成數組的下標之后,在通過數組下標快速定位到某個位置上,如果這個位置上什么都沒有,則返回null。

如果這個位置上有單向鏈表,那么它就會拿著參數K和單向鏈表上的每一個節點的K進行equals,如果所有equals方法都返回false,則get方法返回null。

如果其中一個節點的K和參數K進行equals返回true,那么此時該節點的value就是我們要找的value了,get方法最終返回這個要找的value。


java 1.7 和 java1.8 HashMap 的區別


jdk1.7中HashMap采用的是位桶+鏈表的方式,即我們常說的散列鏈表的方式,而

jdk1.8中采用的是位桶+鏈表/紅黑樹的方式,也是非線程安全的。當某個位桶的鏈表的長度達到某個閥值(8)的時候,這個鏈表就將轉換成紅黑樹。

在jdk1.8中,如果鏈表長度大于8且節點數組長度大于64的時候,就把鏈表下所有的節點轉為紅黑樹。

樹形化還有一個要求就是數組長度必須大于等于64,否則繼續采用擴容策略

總的來說,HashMap默認采用數組+單鏈表方式存儲元素,當元素出現哈希沖突時,會存儲到該位置的單鏈表中。但是單鏈表不會一直增加元素,當元素個數超過8個時,會嘗試將單鏈表轉化為紅黑樹存儲。但是在轉化前,會再判斷一次當前數組的長度,只有數組長度大于64才處理。否則,進行擴容操作。


HashMap鏈表轉紅黑樹


當鏈表長度大于或等于閾值(默認為 8)的時候,如果同時還滿足容量大于或等于 MIN_TREEIFY_CAPACITY(默認為 64)的要求,就會把鏈表轉換為紅黑樹。

同樣,后續如果由于刪除或者其他原因調整了大小,當紅黑樹的節點小于或等于 6 個以后,又會恢復為鏈表形態。

? ? ? ?每次遍歷一個鏈表,平均查找的時間復雜度是 O(n),n 是鏈表的長度。紅黑樹有和鏈表不一樣的查找性能,由于紅黑樹有自平衡的特點,可以防止不平衡情況的發生,所以可以始終將查找的時間復雜度控制在 O(log(n))。最初鏈表還不是很長,所以可能 O(n) 和 O(log(n)) 的區別不大,但是如果鏈表越來越長,那么這種區別便會有所體現。所以為了提升查找性能,需要把鏈表轉化為紅黑樹的形式。

? ? ? ?還要注意很重要的一點,單個 TreeNode 需要占用的空間大約是普通 Node 的兩倍,所以只有當包含足夠多的 Nodes 時才會轉成 TreeNodes,而是否足夠多就是由 TREEIFY_THRESHOLD 的值決定的。而當桶中節點數由于移除或者 resize 變少后,又會變回普通的鏈表的形式,以便節省空間。

? ? ? ?默認是鏈表長度達到 8 就轉成紅黑樹,而當長度降到 6 就轉換回去,這體現了時間和空間平衡的思想,最開始使用鏈表的時候,空間占用是比較少的,而且由于鏈表短,所以查詢時間也沒有太大的問題。可是當鏈表越來越長,需要用紅黑樹的形式來保證查詢的效率。

? ? ? ?在理想情況下,鏈表長度符合泊松分布,各個長度的命中概率依次遞減,當長度為 8 的時候,是最理想的值。

? ? ? ?事實上,鏈表長度超過 8 就轉為紅黑樹的設計,更多的是為了防止用戶自己實現了不好的哈希算法時導致鏈表過長,從而導致查詢效率低,而此時轉為紅黑樹更多的是一種保底策略,用來保證極端情況下查詢的效率。

? ? ? ?通常如果 hash 算法正常的話,那么鏈表的長度也不會很長,那么紅黑樹也不會帶來明顯的查詢時間上的優勢,反而會增加空間負擔。所以通常情況下,并沒有必要轉為紅黑樹,所以就選擇了概率非常小,小于千萬分之一概率,也就是長度為 8 的概率,把長度 8 作為轉化的默認閾值。

? ? ? ?如果開發中發現 HashMap 內部出現了紅黑樹的結構,那可能是我們的哈希算法出了問題,所以需要選用合適的hashCode方法,以便減少沖突。?
?


總結

HashMap基于哈希散列表實現 ,可以實現對數據的讀寫。

將鍵值對傳遞給put方法時,它調用鍵對象的hashCode()方法來計算hashCode,然后找到相應的bucket位置(即數組)來儲存值對象。

當獲取對象時,通過鍵對象的equals()方法找到正確的鍵值對,然后返回值對象。

HashMap使用鏈表來解決hash沖突問題,當發生沖突了,對象將會儲存在鏈表的頭節點中。HashMap在每個鏈表節點中儲存鍵值對對象,當兩個不同的鍵對象的hashCode相同時,它們會儲存在同一個bucket位置的鏈表中,如果鏈表大小超過閾值(TREEIFY_THRESHOLD,8),鏈表就會被改造為樹形結構。

1.7擴容時需要重新計算哈希值和索引位置,1.8并不重新計算哈希值,巧妙地采用和擴容后容量進行&操作來計算新的索引位置。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/212113.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/212113.shtml
英文地址,請注明出處:http://en.pswp.cn/news/212113.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

properties配置和讀取

如何配置和讀取屬性文件 1.屬性文件介紹1.1 什么是屬性文件1.2屬性文件規范1.3 屬性文件優缺點 2.屬性文件讀取4.spring和屬性文件4.1利用注解讀取4.2配置文件里直接引用 4.屬性文件寫入5.注意事項5.總結 1.屬性文件介紹 1.1 什么是屬性文件 Java開發中&#xff0c;我們經常需…

Qt6.5類庫實例大全:Qt Creator快速入門

哈嘍大家好&#xff0c;我是20YC編程小二&#xff01;掃碼關注公眾號&#xff0c;現在可免費領取《C程序員》在線視頻教程哦&#xff01;#下面開始今天內容# 1. Qt Creator介紹 Qt Creator是一個輕量級的跨平臺集成開發環境(IDE)&#xff0c;專為使用Qt框架進行應用程序開發而…

華為OD機試真題-攀登者1-2023年OD統一考試(C卷)

題目描述: 攀登者喜歡尋找各種地圖,并且嘗試攀登到最高的山峰。 地圖表示為一維數組,數組的索引代表水平位置,數組的高度代表相對海拔高度。其中數組元素0代表地面。 例如[0,1,2,4,3,1,0,0,1,2,3,1,2,1,0], 代表如下圖所示的地圖,地圖中有兩個山脈位置分別為 1,2,3,4,5和8…

基于深度學習的文本分類研究綜述

摘要 與傳統的機器學習模型相比&#xff0c;深度學習模型試圖模仿人的學習思路&#xff0c;通過計算機自動進行海量數據的特征提取工作。文本分類是自然語言處理中的一個重要應用&#xff0c;在文本信息處理過程中有著關鍵作用。過去幾年&#xff0c;由于深度學習研究的空前成…

NAND閃存市場2023年Q3增長2.9%,Q4有望激增20%

TrendForce報告顯示&#xff0c;NAND閃存市場在2023年第三季度出現了關鍵轉折&#xff0c;主要由三星的戰略性減產決定驅動。最初&#xff0c;市場對終端用戶需求的不確定性以及對平淡旺季的擔憂導致買家采取保守的方法&#xff0c;庫存低、采購慢。然而&#xff0c;隨著三星等…

華為新款筆記本搭載5nm麒麟芯片,來源成謎,可能讓大家失望了~

近日&#xff0c;華為公司悄悄推出了一款基于國產技術打造的全新商用筆記本——華為擎云L540。目前&#xff0c;華為擎云L540在京東平臺悄然上線的&#xff0c;尚未在華為官方渠道公開售賣。華為擎云L540搭載了麒麟9006C處理器&#xff0c;采用先進的5nm制程工藝&#xff0c;8 …

codeforces A. Morning

思路 模擬&#xff0c;按順序移動移動到對應位置貢獻為移動的步數&#xff0c;press的次數。 Think Twice, Code Once #include<bits/stdc.h> #define il inline #define get getchar #define put putchar #define is isdigit #define int long long #define dfor(i,a…

openGauss學習筆記-150 openGauss 數據庫運維-備份與恢復-物理備份與恢復之gs_backup

文章目錄 openGauss學習筆記-150 openGauss 數據庫運維-備份與恢復-物理備份與恢復之gs_backup150.1 背景信息150.2 前提條件150.3 語法150.4 參數說明150.5 示例 openGauss學習筆記-150 openGauss 數據庫運維-備份與恢復-物理備份與恢復之gs_backup 150.1 背景信息 openGaus…

錯題總結(四)

1.【一維數組】輸入10個整數&#xff0c;求平均值 編寫一個程序&#xff0c;從用戶輸入中讀取10個整數并存儲在一個數組中。然后&#xff0c;計算并輸出這些整數的平均值。 int main() {int arr[10];int sum 0;for (int n 0; n < 10; n){scanf("%d", &arr…

[完美解決]Accelerate設置單卡訓練報錯,成功設置單卡訓練

報錯內容 ValueError: Less than two GPU ids were configured and tried to run on on multiple GPUs. Please ensure at least two are specified for --gpu_ids, or use --gpu_idsall. ValueError:配置了少于兩個GPU id&#xff0c;并試圖在多個GPU上運行。請確保為——gpu…

小黑子——springBoot基礎

springBoot簡單學習 一、SpringBoot簡介1.1 springBoot快速入門1.1.1 開發步驟1.1.2 對比1.1.3 官網構建工程1.1.3 SpringBoot工程快速啟動 1.2 springBoot概述1.2.1 起步依賴I. 探索父工程II. 探索依賴III. 小結 1.2.2 程序啟動1.2.3 切換web服務器-jetty 二、配置文件2.1 配置…

C語言精選——選擇題Day43

第一題 1. 使用malloc系統調用分配的內存是在什么上分配的&#xff1f; A&#xff1a;棧 B&#xff1a;堆 答案及解析 B malloc開辟的空間都是在堆上申請的內存空間&#xff0c;但是我們平常定義的定長數組之類的&#xff0c;都是在棧上開辟的空間&#xff1b; 第二題 2. C語言…

scala變量與變量類型

1.6 變量與類型&#xff08;重點&#xff09;1.6.1 變量推斷1.6.2 多變量定義1.6.3 var和val的區別 1.6.3.1 是否可變 1.6.3.2 延遲加載 1.6 變量與類型&#xff08;重點&#xff09; val修飾的變量&#xff0c;相當于Java中final修飾的變量; // 定義常量s1&#xff0c;使用…

[每周一更]-(第76期):Go源碼閱讀與分析的方式

讀源碼可以深層理解Go的編寫方式&#xff0c;理解作者們的思維方式&#xff1b;也有助于對Go語法用法深刻的理解&#xff0c;我們從這一篇說一下如何讀源碼&#xff0c;從哪些源碼著手&#xff0c;從 簡單到深入的方式學習源碼&#xff1b; 學習源碼也是一個修煉過程&#xff0…

「斗破年番」卡點俠蕭炎又卡點救人,四長老毒氣攻心,黑皇城尋寶

Hello,小伙伴們&#xff0c;我是拾荒君。 《斗破蒼穹年番》第74集如約而至&#xff0c;帶給觀眾們更多的驚喜與感動。這一集中&#xff0c;蕭炎的體內魔毒斑暫時被厄難毒體所壓制&#xff0c;他決定回到迦南學院&#xff0c;尋求斗尊強者的幫助解決這個問題。然而&#xff0c;…

深入理解 Flask 中的 Session 和 Cookies

在構建 web 應用時,管理用戶的狀態和數據是至關重要的。Flask,作為一個靈活的微型 web 框架,提供了會話(Session)和 Cookies 管理的能力。本文將深入探討 Flask 中的會話和 Cookies 的概念、工作機制以及應用實例,為讀者提供全面而詳細的理解。 會話和 Cookies 的基本概…

【LeetCode熱題100】【滑動窗口】找到字符串中所有字母異位詞

給定兩個字符串 s 和 p&#xff0c;找到 s 中所有 p 的 異位詞 的子串&#xff0c;返回這些子串的起始索引。不考慮答案輸出的順序。 異位詞 指由相同字母重排列形成的字符串&#xff08;包括相同的字符串&#xff09;。 示例 1: 輸入: s "cbaebabacd", p "…

611.有效的三角形個數

1.題目解析 給定一個包含非負整數的數組 nums &#xff0c;返回其中可以組成三角形三條邊的三元組個數。 補充&#xff1a; 1.三角形的判斷&#xff1a;假設有三條邊按大小排序&#xff1a; 2.題目示例 示例 1: 輸入: nums [2,2,3,4] 輸出: 3 解釋:有效的組合是: 2,3,4 (使用…

P1161 開燈題解

題目 在一條無限長的路上&#xff0c;有一排無限長的路燈&#xff0c;編號為1,2,3,4,…。 每一盞燈只有兩種可能的狀態&#xff0c;開或者關。如果按一下某一盞燈的開關&#xff0c;那么這盞燈的狀態將發生改變。如果原來是開&#xff0c;將變成關。如果原來是關&#xff0c;…

C現代方法(第27章)筆記——C99對數學計算的新增支持

文章目錄 第27章 C99對數學計算的新增支持27.1 <stdint.h>: 整數類型(C99)27.1.1 <stdint.h>類型27.1.2 對指定寬度整數類型的限制27.1.3 對其他整數類型的限制27.1.4 用于整型常量的宏 27.2 <inttype.h>: 整數類型的格式轉換(C99)27.2.1 用于格式指定符的宏…