『壹』 通過實現原理及源代碼分析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中對於
『伍』 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。我回答的通俗易懂把!!!