導航:首頁 > 源碼編譯 > 源碼分析hashmap

源碼分析hashmap

發布時間:2023-07-28 07:17:07

『壹』 通過實現原理及源代碼分析HashMap該怎麼用

HashMap
,都知道哪裡要用
HashMap
,知道
Hashtable

HashMap
之間的區別
,那麼
為何這道面試題如此特殊呢?是因為這道題考察的深度很深。
這題經常出現在高級或中高級
面試中。投資銀行更喜歡問這個問題,甚至會要求你實現
HashMap
來考察你的編程能力。
ConcurrentHashMap
和其它同步集合的引入讓這道題變得更加復雜。讓我們開始探索的
旅程吧!

『貳』 CRUD 程序員勿進JDK 源碼剖析手冊與並發編程圖冊,完美詮釋高並發

在後端程序員的中,如果你有高並發的經驗,也往往能得到比別人更多的機會。原因很簡單,高並發會涉及到各種各樣的技術問題,如介面響應超時、GC 頻繁、大數據量儲存、死鎖等等。能搞定高並發的程序員,技術深度一定不會差。

為了能大家更好地了解 java 並發,我我前段時間特地從阿里的一位朋友手上拿到了他手寫的 JDK 源碼剖析手冊與並發編程圖冊,下面一起來看看吧:

JDK 源碼剖析手冊

基於 JDK7 和 JDK 8,對整個 Concurrent 包進行全面的源碼剖析。JDK8 中大部分並發功能的實現和 JDK 7 一樣,但新增了一些額外的特性。例如 CompletableFuture、ConcurrentHashMap 的新實現、StampedLock、LongAdder 等。



點擊並拖拽以移動



點擊並拖拽以移動



點擊並拖拽以移動


詳細內容展示


點擊並拖拽以移動



點擊並拖拽以移動



點擊並拖拽以移動



點擊並拖拽以移動



點擊並拖拽以移動

並發編程圖冊

包含大量圖文以及源碼分析幫助大家理解相關知識,藉助這些圖文,按照這個順序學習,至少可以讓你簡單入門並發。

總覽



點擊並拖拽以移動


詳細內容展示



點擊並拖拽以移動



點擊並拖拽以移動



點擊並拖拽以移動



點擊並拖拽以移動



點擊並拖拽以移動



點擊並拖拽以移動



點擊並拖拽以移動


由於這份資料實在是細節內容實在太多啦,所以只能把部分知識點截圖出來粗略的介紹,每個小節點裡面都有更細化的內容!如果有需要完整版的朋友可以: 一鍵三連支持一下私信我,注意回復【000】即可獲取更多免費資料! 希望大家看完之後能夠有所收獲~

『叄』 HashMap擴容機制

之前寫過一篇專門介紹HashMap的文章,反響很不錯,不過在留言區問得最多的問題就是HashMap的負載因子初始值為什麼是0.75,私下又好好地研究了一番,總結了這篇文章。

本篇文章基於JDK1.8,特在此說明。

OK。下面我們就開始進行分析。

HashMap源碼分析(jdk1.8,保你能看懂)

一、負載因子的作用

對於HashMap的研究,我之前一直停留在考慮源碼是如何實現的,現在當我重新再來看的時候,才發現,系統默認的各種參數值,才是HashMap的精華所在。

負載因子是和擴容機制有關的,意思是如果當前容器的容量,達到了我們設定的最大值,就要開始執行擴容操作。舉個例子來解釋,避免小白聽不懂:

比如說當前的容器容量是16,負載因子是0.75,16*0.75=12,也就是說,當容量達到了12的時候就會進行擴容操作。

他的作用很簡單,相當於是一個擴容機制的閾值。當超過了這個閾值,就會觸發擴容機制。HashMap源碼已經為我們默認指定了負載因子是0.75。

我截取了部分源碼,從這里可以看出,系統默認的負載因子值就是0.75,而且我們還可以在構造方法中去指定。下面我們就正式來分析一下為什麼是默認的0.75。

二、原因解釋(重點)

我們在考慮HashMap的時候,首先要想到的是HashMap只是一個數據結構,既然是數據結構最主要的就是節省時間和空間。負載因子的作用肯定也是節省時間和空間。為什麼節省呢?我們考慮兩種極端情況。

1、負載因子是1.0

我們先看HashMap的底層數據結構

我們的數據一開始是保存在數組裡面的,當發生了Hash碰撞的時候,就是在這個數據節點上,生出一個鏈表,當鏈表長度達到一定長度的時候,就會把鏈表轉化為紅黑樹。

當負載因子是1.0的時候,也就意味著,只有當數組的8個值(這個圖表示了8個)全部填充了,才會發生擴容。這就帶來了很大的問題,因為Hash沖突時避免不了的。當負載因子是1.0的時候,意味著會出現大量的Hash的沖突,底層的紅黑樹變得異常復雜。對於查詢效率極其不利。這種情況就是犧牲了時間來保證空間的利用率。

因此一句話總結就是負載因子過大,雖然空間利用率上去了,但是時間效率降低了。

2、負載因子是0.5

負載因子是0.5的時候,這也就意味著,當數組中的元素達到了一半就開始擴容,既然填充的元素少了,Hash沖突也會減少,那麼底層的鏈表長度或者是紅黑樹的高度就會降低。查詢效率就會增加。

但是,兄弟們,這時候空間利用率就會大大的降低,原本存儲1M的數據,現在就意味著需要2M的空間。

一句話總結就是負載因子太小,雖然時間效率提升了,但是空間利用率降低了。

3、負載因子0.75

經過前面的分析,基本上為什麼是0.75的答案也就出來了,這是時間和空間的權衡。當然這個答案不是我自己想出來的。答案就在源碼上,我們可以看看:

大致意思就是說負載因子是0.75的時候,空間利用率比較高,而且避免了相當多的Hash沖突,使得底層的鏈表或者是紅黑樹的高度比較低,提升了空間效率。

OK,寫到這答案基本上就出來了,一句話能總結的寫成了一篇文章。如有問題,還請批評指正。

『肆』 java7和java8對hashmap做了哪些優化

HashMap的原理介紹x0dx0ax0dx0a此乃老生常談,不作仔細解說。x0dx0a一句話概括之:HashMap是一個散列表,它存儲的內容是鍵值對(key-value)映射。x0dx0ax0dx0aJava 7 中HashMap的源碼分析x0dx0ax0dx0a首先是HashMap的構造函數代碼塊1中,根據初始化的Capacity與loadFactor(載入因子)初始化HashMap.x0dx0a//代碼塊1x0dx0a public HashMap(int initialCapacity, float loadFactor) {x0dx0a if (initialCapacity < 0)x0dx0a throw new IllegalArgumentException("Illegal initial capacity: " +x0dx0a initialCapacity);x0dx0a if (initialCapacity > MAXIMUM_CAPACITY)x0dx0a initialCapacity = MAXIMUM_CAPACITY;x0dx0a if (loadFactor <= 0 || Float.isNaN(loadFactor))x0dx0a throw new IllegalArgumentException("Illegal load factor: " +loadFactor);x0dx0ax0dx0a this.loadFactor = loadFactor;x0dx0a threshold = initialCapacity;x0dx0a init();x0dx0a }x0dx0ax0dx0aJava7中對於的put方法實現相對比較簡單,首先根據 key1 的key值計算hash值,再根據該hash值與table的length確定該key所在的index,如果當前位置的Entry不為null,則在該Entry鏈中遍歷,如果找到hash值和key值都相同,則將值value覆蓋,返回oldValue;如果當前位置的Entry為null,則直接addEntry。x0dx0a代碼塊2x0dx0apublic V put(K key, V value) {x0dx0a if (table == EMPTY_TABLE) {x0dx0a inflateTable(threshold);x0dx0a }x0dx0a if (key == null)x0dx0a return putForNullKey(value);x0dx0a int hash = hash(key);x0dx0a int i = indexFor(hash, table.length);x0dx0a for (Entry e = table[i]; e != null; e = e.next) {x0dx0a Object k;x0dx0a if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {x0dx0a V oldValue = e.value;x0dx0a e.value = value;x0dx0a e.recordAccess(this);x0dx0a return oldValue;x0dx0a }x0dx0a }x0dx0ax0dx0a modCount++;x0dx0a addEntry(hash, key, value, i);x0dx0a return null;x0dx0a }x0dx0ax0dx0a//addEntry方法中會檢查當前table是否需要resizex0dx0a void addEntry(int hash, K key, V value, int bucketIndex) {x0dx0a if ((size >= threshold) && (null != table[bucketIndex])) {x0dx0a resize(2 * table.length); //當前map中的size 如果大於threshole的閾值,則將resize將table的length擴大2倍。x0dx0a hash = (null != key) ? hash(key) : 0;x0dx0a bucketIndex = indexFor(hash, table.length);x0dx0a }x0dx0ax0dx0a createEntry(hash, key, value, bucketIndex);x0dx0a }x0dx0ax0dx0aJava7 中resize()方法的實現比較簡單,將OldTable的長度擴展,並且將oldTable中的Entry根據rehash的標記重新計算hash值和index移動到newTable中去。代碼如代碼塊3中所示,x0dx0a//代碼塊3 --JDK7中HashMap.resize()方法x0dx0avoid resize(int newCapacity) {x0dx0a Entry[] oldTable = table;x0dx0a int oldCapacity = oldTable.length;x0dx0a if (oldCapacity == MAXIMUM_CAPACITY) {x0dx0a threshold = Integer.MAX_VALUE;x0dx0a return;x0dx0a }x0dx0ax0dx0a Entry[] newTable = new Entry[newCapacity];x0dx0a transfer(newTable, initHashSeedAsNeeded(newCapacity));x0dx0a table = newTable;x0dx0a threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);x0dx0a }x0dx0ax0dx0a /**x0dx0a * 將當前table的Entry轉移到新的table中x0dx0a */x0dx0a void transfer(Entry[] newTable, boolean rehash) {x0dx0a int newCapacity = newTable.length;x0dx0a for (Entry e : table) {x0dx0a while(null != e) {x0dx0a Entry next = e.next;x0dx0a if (rehash) {x0dx0a e.hash = null == e.key ? 0 : hash(e.key);x0dx0a }x0dx0a int i = indexFor(e.hash, newCapacity);x0dx0a e.next = newTable[i];x0dx0a newTable[i] = e;x0dx0a e = next;x0dx0a }x0dx0a }x0dx0a }x0dx0ax0dx0aHashMap性能的有兩個參數:初始容量(initialCapacity) 和載入因子(loadFactor)。容量 是哈希表中桶的數量,初始容量只是哈希表在創建時的容量。載入因子 是哈希表在其容量自動增加之前可以達到多滿的一種尺度。當哈希表中的條目數超出了載入因子與當前容量的乘積時,則要對該哈希表進行 rehash 操作(即重建內部數據結構),從而哈希表將具有大約兩倍的桶數。x0dx0a根據源碼分析可以看出:在Java7 中 HashMap的entry是按照index索引存儲的,遇到hash沖突的時候採用拉鏈法解決沖突,將沖突的key和value插入到鏈表list中。x0dx0a然而這種解決方法會有一個缺點,假如key值都沖突,HashMap會退化成一個鏈表,get的復雜度會變成O(n)。x0dx0a在Java8中為了優化該最壞情況下的性能,採用了平衡樹來存放這些hash沖突的鍵值對,性能由此可以提升至O(logn)。x0dx0a代碼塊4 -- JDK8中HashMap中常量定義x0dx0a static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; x0dx0a static final int TREEIFY_THRESHOLD = 8; // 是否將list轉換成tree的閾值x0dx0a static final int UNTREEIFY_THRESHOLD = 6; // 在resize操作中,決定是否untreeify的閾值x0dx0a static final int MIN_TREEIFY_CAPACITY = 64; // 決定是否轉換成tree的最小容量x0dx0a static final float DEFAULT_LOAD_FACTOR = 0.75f; // default的載入因子x0dx0ax0dx0a在Java 8 HashMap的put方法實現如代碼塊5所示,x0dx0a代碼塊5 --JDK8 HashMap.put方法x0dx0a public V put(K key, V value) {x0dx0a return putVal(hash(key), key, value, false, true);x0dx0a }x0dx0ax0dx0a final V putVal(int hash, K key, V value, boolean onlyIfAbsent,x0dx0a boolean evict) {x0dx0a Node[] tab; Node p; int n, i;x0dx0a if ((tab = table) == null || (n = tab.length) == 0)x0dx0a n = (tab = resize()).length; //table為空的時候,n為table的長度x0dx0a if ((p = tab[i = (n - 1) & hash]) == null)x0dx0a tab[i] = newNode(hash, key, value, null); // (n - 1) & hash 與Java7中indexFor方法的實現相同,若i位置上的值為空,則新建一個Node,table[i]指向該Node。x0dx0a else {x0dx0a // 若i位置上的值不為空,判斷當前位置上的Node p 是否與要插入的key的hash和key相同x0dx0a Node e; K k;x0dx0a if (p.hash == hash &&x0dx0a ((k = p.key) == key || (key != null && key.equals(k))))x0dx0a e = p;//相同則覆蓋之x0dx0a else if (p instanceof TreeNode)x0dx0a // 不同,且當前位置上的的node p已經是TreeNode的實例,則再該樹上插入新的node。x0dx0a e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);x0dx0a else {x0dx0a // 在i位置上的鏈表中找到p.next為null的位置,binCount計算出當前鏈表的長度,如果繼續將沖突的節點插入到該鏈表中,會使鏈表的長度大於tree化的閾值,則將鏈表轉換成tree。x0dx0a for (int binCount = 0; ; ++binCount) {x0dx0a if ((e = p.next) == null) {x0dx0a p.next = newNode(hash, key, value, null);x0dx0a if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1stx0dx0a treeifyBin(tab, hash);x0dx0a break;x0dx0a }x0dx0a if (e.hash == hash &&x0dx0a ((k = e.key) == key || (key != null && key.equals(k))))x0dx0a break;x0dx0a p = e;x0dx0a }x0dx0a }x0dx0a if (e != null) { // existing mapping for keyx0dx0a V oldValue = e.value;x0dx0a if (!onlyIfAbsent || oldValue == null)x0dx0a e.value = value;x0dx0a afterNodeAccess(e);x0dx0a return oldValue;x0dx0a }x0dx0a }x0dx0a ++modCount;x0dx0a if (++size > threshold)x0dx0a resize();x0dx0a afterNodeInsertion(evict);x0dx0a return null;x0dx0a }x0dx0ax0dx0a再看下resize方法,由於需要考慮hash沖突解決時採用的可能是list 也可能是balance tree的方式,因此resize方法相比JDK7中復雜了一些,x0dx0a代碼塊6 -- JDK8的resize方法x0dx0a inal Node[] resize() {x0dx0a Node[] oldTab = table;x0dx0a int oldCap = (oldTab == null) ? 0 : oldTab.length;x0dx0a int oldThr = threshold;x0dx0a int newCap, newThr = 0;x0dx0a if (oldCap > 0) {x0dx0a if (oldCap >= MAXIMUM_CAPACITY) {x0dx0a threshold = Integer.MAX_VALUE;//如果超過最大容量,無法再擴充tablex0dx0a return oldTab;x0dx0a }x0dx0a else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&x0dx0a oldCap >= DEFAULT_INITIAL_CAPACITY)x0dx0a newThr = oldThr << 1; // threshold門檻擴大至2倍x0dx0a }x0dx0a else if (oldThr > 0) // initial capacity was placed in thresholdx0dx0a newCap = oldThr;x0dx0a else { // zero initial threshold signifies using defaultsx0dx0a newCap = DEFAULT_INITIAL_CAPACITY;x0dx0a newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);x0dx0a }x0dx0a if (newThr == 0) {x0dx0a float ft = (float)newCap * loadFactor;x0dx0a newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?x0dx0a (int)ft : Integer.MAX_VALUE);x0dx0a }x0dx0a threshold = newThr;x0dx0a @SuppressWarnings({"rawtypes","unchecked"})x0dx0a Node[] newTab = (Node[])new Node[newCap];// 創建容量為newCap的newTab,並將oldTab中的Node遷移過來,這里需要考慮鏈表和tree兩種情況。

『伍』 HashMap是什麼東西

HashMap,中文名哈希映射,HashMap是一個用於存儲Key-Value鍵值對的集合,每一個鍵值對也叫做Entry。這些個鍵值對(Entry)分散存儲在一個數組當中,這個數組就是HashMap的主幹。HashMap數組每一個元素的初始值都是Null。

HashMap是基於哈希表的 Map 介面的實現。此實現提供所有可選的映射操作,並允許使用 null 值和 null 鍵。(除了非同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證映射的順序,特別是它不保證該順序恆久不變。

(5)源碼分析hashmap擴展閱讀:

因為HashMap的長度是有限的,當插入的Entry越來越多時,再完美的Hash函數也難免會出現index沖突的情況。

HashMap數組的每一個元素不止是一個Entry對象,也是一個鏈表的頭節點。每一個Entry對象通過Next指針指向它的下一個Entry節點。當新來的Entry映射到沖突的數組位置時,只需要插入到對應的鏈表即可。

『陸』 jdk1.8 HashMap擴容原理解析

最近看面試題有聊到hashmap擴容,追本溯源,追到了1.8版本resize方法做的核心改進,找了資料一直也看不太懂,最後苦苦冥思總算弄懂了,在此做下筆記,也是做下分享,分享給同樣雲里霧里的碼友。時間有限,所以研究的不全面,後期會陸續更新。

在講解源碼之前還是要先做下鋪墊。。。

&運算是二進制位運算符中的一種

簡單來說

------------------------------------------------------

1 & 1 = 1                1 & 0 = 0

0 & 1 = 0                0 & 0 = 0

------------------------------------------------------

兩個操作數都為1,結果才為1,否則為0

說到這里,再說一下與運算很重要的一個技巧:取位

舉個例子,我們有這樣一個二進制    1010  0101

我們想取出看看第3位是0還是1,怎麼做呢,我們這樣

為了方便理解,轉換一下

------------------------------------------------------

------------------------------------------------------

我們看,下面只有第三位為1,其他全部為0,那麼上面對應的位置肯定也為0

而第三位為1,那麼結果就取決於上面對應第三位是0還是1了

如果是1  那麼 1 & 1 = 1

如果是0 那麼 0 & 0 = 0

如此我們計算上面的結果

那如果是取第四位呢,同理

------------------------------------------------------

------------------------------------------------------

結果顯而易見

hashmap的數組長度,一定是2的次冪,其擴容就是長度直接擴2倍。

當散列表很大,節點很擁擠,鏈表會大量的出現,但是鏈表的查詢速率很低,若節點數達到了載入因子的擴容條件,

——————————————————

註:雖然鏈表長度大於等於8會轉化成紅黑樹,但是我們還是要盡量減少鏈表出現的概率,要使得節點更加分散。於是有了載入因子。0.75是很好的一個折中,因為擴容是很消耗資源的。

——————————————————

這時為了減少hash沖突得情況,減少鏈表出現的概率,我們得對hashmap進行擴容,並對node節點進行一次重新分布,使其分布得更均勻一些,怎麼做呢。

我們看,假設這個hashmap的情況現在是這樣

假設,它很擠了,需要擴容,那麼擴容變成這樣,再加一倍長度變成32

僅僅是這樣還不行,否則沒有最大限度利用擴容所做的犧牲(資源消耗),我們需要對node節點進行重新分布

怎麼做呢,我們可以拿一部分節點,放到擴容出來的空間上,也就是

舉個例子,假設我們把2和6拿過去,那就是變成這樣

如此一來,原本的節點順序沒有發生太大的改動,新的空間得到了利用,節點分布也更均勻,鏈表出現的概率也更少,計算也更加簡單

這就是jdk1.8的hashmap改動的巧妙之處。

這里還有一個問題,我們拿哪一部分,換句話說,怎麼決定這個節點是不變還是移動到新的位置

為了降低hash沖突,我們得讓它自己決定,我們要充分利用隨機的特性,隨機才會更加均勻

下面看源碼實現

我們都知道,hashmap的初始默認容量為16,換成二進制就是    1 0000    (16=2^5-1)

但是,數組下標是從0開始的,那麼16-1=15,換成二進制就是    0 1111

那麼擴容一次呢,長度會變成32,那麼32-1=31 ,換成二進制就是    01 1111

就是在1111的基礎上加了一位而已,我們知道,node節點是不變的,那麼它的hash也不會變。

假設在經過擾動以後 hash = 1010  0101

對16-1進行與運算

——————————————————

------------------------------------------------

結果自然是0101 = 2^2 + 2^0 = 4 + 1 = 5

在擴容後,長度32,31的二進制是0001 1111

對比原來的hash

———————————————————

----------------------------------------------------

分析,我們便可以知道,hash是隨機的,那麼其第5位也是隨機的

那我們只要把第5位取出來,看看,

結合第一節,我們只需這樣做

轉換一下

—————————————————

1010  0101

0001  0000    (16)

--------------------------------------------

結果為0,所以這個節點不移動。

那如果是1011 0101,那麼這個節點就要移動

標黃的地方就是整個的精髓所在,e是當前判斷是否要移動的節點,oldCap就是原數組長度。

今天先到這里,如有疑問,可在下方評論,我後續更新補充。本文若有不妥之處,歡迎指正。謝謝觀看。

『柒』 怎麼看hashmap和hashtable的源碼

HashMap是Hashtable的輕量級實現(非線程安全的實現),他們都完成了Map介面,主要區別在於HashMap准許空(Null)鍵值(Key),由於非線程安全,效率上可能高於Hashtable。我回答的通俗易懂把!!!

閱讀全文

與源碼分析hashmap相關的資料

熱點內容
dvd光碟存儲漢子演算法 瀏覽:755
蘋果郵件無法連接伺服器地址 瀏覽:958
phpffmpeg轉碼 瀏覽:669
長沙好玩的解壓項目 瀏覽:140
專屬學情分析報告是什麼app 瀏覽:562
php工程部署 瀏覽:831
android全屏透明 瀏覽:730
阿里雲伺服器已開通怎麼辦 瀏覽:801
光遇為什麼登錄時伺服器已滿 瀏覽:300
PDF分析 瀏覽:482
h3c光纖全工半全工設置命令 瀏覽:140
公司法pdf下載 瀏覽:379
linuxmarkdown 瀏覽:349
華為手機怎麼多選文件夾 瀏覽:681
如何取消命令方塊指令 瀏覽:347
風翼app為什麼進不去了 瀏覽:776
im4java壓縮圖片 瀏覽:360
數據查詢網站源碼 瀏覽:148
伊克塞爾文檔怎麼進行加密 瀏覽:888
app轉賬是什麼 瀏覽:161