1. 程式人生 > >學的會一些HashMap知識和疑惑

學的會一些HashMap知識和疑惑

在日常開發中使用過的java集合類有哪些?
一般應聘者都會回答ArrayList,LinkedList,HashMap,HashSet等等。如果連這幾個集合類都不知道,基本上可以pass了。(這幾個都要找來看看原始碼解析和常用的方法)

記住陣列的長度是不能改變的,擴容也只是在做資料的遷移

只能有一個K為null值的元素,可以有多個V值為null的元素

1.jdk7那個內部類叫entry,jdk8叫node

2.jdk8用的紅黑二叉樹,在一條連結串列上元素大於8個時(就是9個或以上),轉為紅黑二叉樹,在元素少於或者等於6個時轉為連結串列,自平衡紅黑二叉樹,解決了單純連結串列在hash衝突嚴重時,連結串列過長,查詢效率太低的情況,紅黑二叉樹的查詢次數,不會大於樹的高度.

自平衡紅黑二叉樹的建立規則,變色,左旋,右旋

3.jdk7在擴容時,是重新對k進行hash操作,然後對新擴容後的陣列長度取模來獲取index,其實這是浪費效能的,因為雜湊桶的長度都總會是2的N次放,所以在jdk1.8優化了,只需要看看原來的hash值新增的那個高位bit是1還是0就好了,是0的話索引沒變,是1的話索引變成“原索引+oldCap”(還是沒搞明白怎麼優化,不重新計算出來那怎麼去看呢,還說什麼新增的那個高位bit是1還是0,可認為是隨機的,那儲存位置是原索引還是原索引加oldCap也是隨機的了,那要取的時候怎麼取呢(也隨機在這2個位置取,1個位置沒取到,再到另一個位置取??))

4.jdk7擴容時連結串列會倒序,而1.8不會

 

支援:

1.取模運算用位與運算來優化(是用數學公式換算得來),大大加快計算時間(計算機操作位與更快)

2.hash操作,這裡用的先取得K值的hashcod值後,(疑問:hashcode值都是一個32位的二進位制數嗎??),做位運算>>16,右移16位,(相當於是除以2的16次方的意思),然後再與原hashcod值進行異或運算,得到一個最終數,然後再取模運算得到index(不用hashcode原值直接進行取模運算,而要這樣做的原因是:主要是從速度、功效、質量來考慮的,這麼做可以在陣列table的length比較小的時候,也能保證考慮到高低Bit都參與到Hash的計算中,同時不會有太大的開銷。)

//hash演算法我們一般說的就是hashcode()方法,這是可以被重寫的方法(繼承自Object),所以有很多種hash演算法,,hashCode()不要求唯一但是要儘可能的均勻分佈,而且演算法效率要儘可能的快。如果面試者能回答出一些常用的演算法,比如MurMurHash(萌萌噠的名字)基本上已經可以俘獲面試官了。

3.原始碼中modcount記錄的是資料結構改變的次數(增加,刪除等這些操作都會改變資料的結構,但是覆蓋不算),適用於快速失敗的(還沒搞懂這個意思)

4.size是記錄的HashMap中存在多少個元素,而不是記錄的有多少個桶中有元素,空參初始化的map有16個空桶,但size是0.

5.threshold閥值,等於雜湊桶的長度x負載因子(可以增加到1,但一般不動,負載因子太小,就說明會頻繁擴容,消耗效能和記憶體,但相對來說,Hash碰撞減少,查取刪效率提高,負載因子過大,節省了記憶體,降低擴容次數,增加hash碰撞機率,查取刪效率降低)

6.k為null值的資料,是儲存在index為0的雜湊桶裡(要麼就是在頭部,要麼就是在連結串列上,這是原始碼中就這麼存K為null的元素的)(問題:null的hashcode值是0嗎,null因該是沒有hashcode值吧,null沒法呼叫hashcode()方法,這是要空指標的?)

7.解決hash碰撞的方法有那些(不單只是說hashMap裡):開放定址法、鏈地址法(也叫開鏈法,HashMap用的方法)、再雜湊法。

8.HashMap怎麼個執行緒不安全,====

9.怎麼讓它變為安全的,Collection.synchronizeMap(map)方法,可以變把HashMap變為安全的,或者用ConcurrentHashMap是執行緒安全的,也可以用Hashtable是執行緒安全的,它的每個方法中都加入了Synchronize方法。在多執行緒併發的環境下,可以直接使用Hashtable,不需要自己為它的方法實現同步,但一般不用它,因為它為了多執行緒安全,對整個資料進行了鎖定,效率低,而我們這時為了執行緒安全,會用ConcurrentHashMap,它使用了分段鎖,並不對整個資料進行鎖定,安全的同時保證一定的效率.

10.為什麼HashMap的雜湊桶長度,要選擇為2的冪次方,並且初始化為16,而不是8也不是32,也不是15也不是17,有博文,首選是雜湊桶的長度我們在new一個HashMap的時候,我們是可以手動指定,比如我們指定為15,但在原始碼中,物件初始化的時候,還是會為我們選擇大於15的,但又最接近15的2的冪次方數,所以內部還是變成了16,然後就是一個操作後的雜湊數,對一個2的冪次方數進行取模運算,產生的雜湊碰撞概率是最小的,也就是說可以更均勻的分散的存資料,降低連結串列的長度,提高增查刪的效率.至於初始為什麼為16,明天再來找資料

 

 

put操作,做了哪些事情:

1.對k的hashcod做hash操作,然後取模得出它在Hash桶的index位置

2.看該index位置是否有元素,沒有就直接插入

3.有,就遍歷這條連結串列(或紅黑二叉樹),逐個比較hashcode值,有相等的且equals也相等的,就覆蓋V值,沒有相等hashcode值的話,就準備插入.

4.但在插入前,先判斷是否需要擴容,不需要的話

5.就將元素插入這個雜湊桶位置,然後將它的next屬性指向老的元素

6.如果判斷觸發擴容,執行擴容操作,新建一個map,雜湊桶的陣列長度X2

7.遍歷雜湊桶陣列,並且遍歷每一個桶的連結串列(或紅黑二叉樹),按照新的x2的陣列長度重新取模,得到各自新的index位置(要麼就是原位置,要麼就是原位置加老的陣列長度,jdk8對這個演算法有優化),jdk7的話,因為put的時候連結串列就是有序的(一條連結串列,後put進來的會在頭位置),這裡是遍歷到index頭位置後,再按照next屬性來逐個遍歷這條連結串列,所以再將這些元素按照新的取模值插入到擴容後的雜湊桶中時,順序就反過來了.(而jdk8不會)

8.執行完這部擴容操作後,才將最新要put進來的元素重新取模,得到它要插入hash桶的index位置

9.如果這個inde位置上沒有元素,就直接插入,有,就遍歷這條連結串列(或紅黑二叉樹),逐個比較hashcode值,有相等的且equals也相等的,就覆蓋V值,沒有相等hashcode值的話,就將元素插入這個雜湊桶位置,然後將它的next屬性指向老的元素