內容和標題同樣長哦,人家寫了很久的。如無特別指明,內容對應的源碼是jdk1.7(後面會和1.8對比)java
1:hashmap簡介(以下,數組-鏈表形式)mysql
HashMap的存儲結構算法
圖中,紫色部分即表明哈希表,也稱爲哈希數組(默認數組大小是16,每對key-value鍵值對實際上是存在map的內部類entry裏的),數組的每一個元素都是一個單鏈表的頭節點,跟着的綠色鏈表是用來解決衝突的,若是不一樣的key映射到了數組的同一位置處,就會採用頭插法將其放入單鏈表中。sql
2:hashmap原理(即put和get原理)編程
2.1 put原理數組
1.根據key獲取對應hash值:int hash = hash(key.hash.hashcode())安全
2.根據hash值和數組長度肯定對應數組引int i = indexFor(hash, table.length); 簡單理解就是i = hash值%模以 數組長度(實際上是按位與運算)。若是不一樣的key都映射到了數組的同一位置處,就將其放入單鏈表中。且新來的是放在頭節點。數據結構
2.2 get原理多線程
1.經過hash得到對應數組位置,遍歷該數組所在鏈表(key.equals())併發
3.1:hashcode相同,衝突怎麼辦?
「頭插法」,放到對應的鏈表的頭部。
3.2:爲何是頭插法(爲何這麼設計)?
由於HashMap的發明者認爲,後插入的Entry被查找的可能性更大,因此放在頭部(由於get()查詢的時候會遍歷整個鏈表)。
4.1:hashmap的默認數組長度是多少?
默認爲16
4.2:爲何?
之因此選擇16,是爲了服務於從key映射到index的hash算法(看下面)。
5.1:hashmap達到默認負載因子(0.75)怎麼辦?
自動雙倍擴容,擴容後從新計算每一個鍵值對位置。且長度必須爲16或者2的冪次
5.2:爲啥要16或者2的冪次?
若不是16或者2的冪次,位運算的結果不夠均勻分佈,顯然不符合Hash算法均勻分佈的原則。
反觀長度16或者其餘2的冪,Length-1的值是全部二進制位全爲1,這種狀況下,index的結果等同於HashCode後幾位的值。只要輸入的HashCode自己分佈均勻,Hash算法的結果就是均勻的。
6.1:hashmap是線程安全的嗎?
不是。
6.2 :爲何?
由於沒加鎖
6.3: 那在併發時會致使什麼問題?
hashmap在接近臨界點時,若此時兩個或者多個線程進行put操做,都會進行resize(擴容)和ReHash(爲key從新計算所在位置),而ReHash在併發的狀況下可能會造成鏈表環。在執行get的時候,會觸發死循環,引發CPU的100%問題。
注:jdk8已經修復hashmap這個問題了,jdk8中擴容時保持了原來鏈表中的順序。可是HashMap還是非併發安全,在併發下,仍是要使用ConcurrentHashMap。
6.4: 如何判斷有環形表?
最優:首先建立兩個指針A和B(在java裏就是兩個對象引用),同時指向這個鏈表的頭節點。而後開始一個大循環,在循環體中,讓指針A每次向下移動一個節點,讓指針B每次向下移動兩個節點,而後比較兩個指針指向的節點是否相同。若是相同,則判斷出鏈表有環,若是不一樣,則繼續下一次循環。
理解例子:在一個環形跑道上,兩個運動員在同一地點起跑,一個運動員速度快,一個運動員速度慢。當兩人跑了一段時間,速度快的運動員必然會從速度慢的運動員身後再次追上並超過,緣由很簡單,由於跑道是環形的。
7: hashmap 和 hashtable 區別?
二者的區別線程效率數組默認值null值hashmap不安全更高16key-value都容許hashtable安全略低11不容許(拋異常)
8.0:那hashmap不安全,hashtable性能又低,怎麼辦?
用concurrenthashmap,即保證安全,性能又能夠保證。
8.1:那concurrenthashmap到底是什麼?
整個ConcurrentHashMap的結構以下:
理解:hashmap是有entry數組組成,而concurrenthashmap則是Segment數組組成。而Segment又是什麼呢?Segment自己就至關於一個HashMap。
同HashMap同樣,Segment包含一個HashEntry數組,數組中的每個HashEntry既是一個鍵值對,也是一個鏈表的頭節點。
單一的Segment結構以下(是否是看着就是hashmap):
像這樣的Segment對象,在ConcurrentHashMap集合中有多少個呢?有2的N次方個,共同保存在一個名爲segments的數組當中。
能夠說,ConcurrentHashMap是一個二級哈希表。在一個總的哈希表下面,有若干個子哈希表。(這樣類比理解多個hashmap組成一個cmap)
8.2:那他的put和get方法呢?
Put方法:
1.爲輸入的Key作Hash運算,獲得hash值。
2.經過hash值,定位到對應的Segment對象
3.獲取可重入鎖
4.再次經過hash值,定位到Segment當中數組的具體位置。
5.插入或覆蓋HashEntry對象。
6.釋放鎖。
Get方法:
1.爲輸入的Key作Hash運算,獲得hash值。
2.經過hash值,定位到對應的Segment對象
3.再次經過hash值,定位到Segment當中數組的具體位置。
因而可知,和hashmap相比,ConcurrentHashMap在讀寫的時候都須要進行二次定位。先定位到Segment,再定位到Segment內的具體數組下標。
9: hashmap 和 concurrenthashmap區別?
線程: 不安全 安全
10.1:爲啥concurrenthashmap和hashtable都是線程安全,可是前者性能更高
由於前者是用的分段鎖,根據hash值鎖住對應Segment對象,當hash值不一樣時,使其能實現並行插入,效率更高,而hashtable則會鎖住整個map。
如何理解並行插入:當cmap須要put元素的時候,並非對整個map進行加鎖,而是先經過hashcode來知道他要放在那一個分段(Segment對象)中,而後對這個分段進行加鎖,因此當多線程put的時候,只要不是放在同一個分段中,就實現了真正的並行的插入。
可是,在統計size的時候,就是獲取concurrenthashmap全局信息的時候,就須要獲取全部的分段鎖才能統計(即效率稍低)。
10.2:分段鎖的設計解決的是什麼問題?
分段鎖的設計目的是細化鎖的粒度,當操做不須要更新整個數組的時候,就僅僅針對數組中的一部分行加鎖操做。
11:JDK1.7的hashmap和JDK1.8的hashmap的區別(即1.8作了哪些優化)?
1.爲了加快查詢效率,java8的hashmap引入了紅黑樹結構,當數組長度大於默認閾值64時,且當某一鏈表的元素>8時,該鏈表就會轉成紅黑樹結構,查詢效率更高。(問題來了,什麼是紅黑樹?什麼是B+樹?(mysql索引有B+樹索引)什麼是B樹?什麼是二叉查找樹?)數據結構方面的知識點會更新在【數據結構專題】,這裏不展開。
這裏只簡單的介紹一下紅黑樹:
紅黑樹是一種自平衡二叉樹,擁有優秀的查詢和插入/刪除性能,普遍應用於關聯數組。對比AVL樹,AVL要求每一個結點的左右子樹的高度之差的絕對值(平衡因子)最多爲1,而紅黑樹經過適當的放低該條件(紅黑樹限制從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長,結果是這個樹大體上是平衡的),以此來減小插入/刪除時的平衡調整耗時,從而獲取更好的性能,而這雖然會致使紅黑樹的查詢會比AVL稍慢,但相比插入/刪除時獲取的時間,這個付出在大多數狀況下顯然是值得的。好了我知道大家看暈了,移步去看看個人【數據結構專題】吧。
2.優化擴容方法,在擴容時保持了原來鏈表中的順序,避免出現死循環
12:JDK1.7的concurrenthashmap和JDK1.8又有什麼區別?
1.8的實現已經拋棄了Segment分段鎖機制,利用Node數組+CAS+Synchronized來保證併發更新的安全,底層採用數組+鏈表+紅黑樹的存儲結構。
java給咱們帶來了併發安全的ConcurrentHashMap,它的實現是依賴於 Java 內存模型,因此咱們在瞭解 ConcurrentHashMap 的以前必須瞭解一些底層的知識:
java內存模型
java中的Unsafe
java中的CAS
java同步器AQS
ReentrantLock
因此在這裏我不許備深刻講解ConcurrentHashMap ,我會在【併發編程】專題經過一步步詳解併發基礎,從java內存模型,synchronized,volatile,Unsafe到CAS,AQS,各類鎖再到JUC併發包相關。
先放張java內存模型的思惟導圖勾引一波,光java內存模型一個點就有這麼多要講的了。
13:那麼問題來了,什麼是CAS?
關於CAS方面的知識點,又會涉及到ABA問題,又能夠扯到樂觀鎖悲觀鎖,鎖編程,AQS等,相關內容將更新在【併發編程專題】,這裏不作展開
14:那1.9的呢?
瞄了一眼,好像和1.8的沒啥區別,這裏不作展開....(別打臉)