就是把任意長度的輸入(又叫作預映射, pre-image),經過散列算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間一般遠小於輸入的空間,不一樣的輸入可能會散列成相同的輸出,因此不可能從散列值來肯定惟一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。經常使用HASH函數:直接取餘法、乘法取整法、平方取中法java
經常使用hash算法的介紹:(1) MD4(2) MD5它對輸入仍以512位分組,其輸出是4個32位字的級聯(3) SHA-1
CopyOnWriteArrayList
在多線程環境下,使用HashMap進行put
操做會引起多線程擴容,因爲JDK1.7中HashMap的鏈表採用頭插法,多線程擴容後會造成環形數據結構,一旦造成環形數據結構,Entry的next節點永遠不爲空,就會產生死循環獲取Entry,致使在get
遍歷查找元素的時候進入死循環,使得CPU利用率接近100%
面試
HashMap之因此在併發下的擴容形成死循環,是由於,多個線程併發進行時,由於一個線程先期完成了擴容,將原Map的鏈表從新散列到本身的表中,而且鏈表變成了倒序,後一個線程再擴容時,又進行本身的散列,再次將倒序鏈表變爲正序鏈表。因而造成了一個環形鏈表,當get表中不存在的元素時,形成死循環
①、HashMap 是線程不安全的,HashTable 是線程安全的算法
②、因爲線程安全,因此 HashTable 的效率比不上 HashMap數組
③、HashMap最多隻容許一條記錄的鍵爲null,容許多條記錄的值爲null,而 HashTable 不容許安全
④、HashMap 默認初始化數組的大小爲16,HashTable 爲 11,前者擴容時,擴大兩倍,後者擴大兩倍+1數據結構
⑤、HashMap 須要從新計算 hash 值,而 HashTable 直接使用對象的 hashCode多線程
ConcurrentHashMap 類(是 Java併發包 java.util.concurrent 中提供的一個線程安全且高效的 HashMap 實現)。併發
HashTable 是使用 synchronize 關鍵字加鎖的原理(就是對對象加鎖);函數
而針對 ConcurrentHashMap,在 JDK 1.7 中採用分段鎖的方式;JDK 1.8 中直接採用了CAS(無鎖算法)+ synchronized,也採用分段鎖的方式並大大縮小了鎖的粒度。高併發
除了加鎖,原理上無太大區別
另外,HashMap 的鍵值對容許有null
(key爲null放在table的0號位置),可是ConCurrentHashMap 都不容許
在數據結構上,紅黑樹相關的節點類
HashTable 使用一把鎖(鎖住整個鏈表結構)處理併發問題,多個線程競爭一把鎖,容易阻塞
ConcurrentHashMap
JDK 1.7 中使用分段鎖(ReentrantLock + Segment + HashEntry),至關於把一個 HashMap 分紅多個段,每段分配一把鎖,這樣支持多線程訪問。鎖粒度:基於 Segment,包含多個 HashEntry。
JDK 1.8 中使用 CAS + synchronized + Node + 紅黑樹。鎖粒度:Node(首結點)(實現 Map.Entry<K,V>)。鎖粒度下降了。
JDK 1.7 中,採用分段鎖的機制,實現併發的更新操做,底層採用數組+鏈表的存儲結構,包括兩個核心靜態內部類 Segment 和 HashEntry。
①、Segment 繼承 ReentrantLock(重入鎖) 用來充當鎖的角色,每一個 Segment 對象守護每一個散列映射表的若干個桶
②、HashEntry 用來封裝映射表的鍵-值對
③、每一個桶是由若干個 HashEntry 對象連接起來的鏈表
JDK 1.8 中,採用Node + CAS + Synchronized來保證併發安全。取消類 Segment,直接用 table 數組存儲鍵值對;當 HashEntry 對象組成的鏈表長度超過 TREEIFY_THRESHOLD 時,鏈表轉換爲紅黑樹,提高性能。底層變動爲數組 + 鏈表 + 紅黑樹
①、重要的常量:
private transient volatile int sizeCtl;
當爲負數時,-1 表示正在初始化,-N 表示 N - 1 個線程正在進行擴容
當爲 0 時,表示 table 尚未初始化
當爲其餘正數時,表示初始化或者下一次進行擴容的大小
②、數據結構:
Node 是存儲結構的基本單元,繼承 HashMap 中的 Entry,用於存儲數據
TreeNode 繼承 Node,可是數據結構換成了二叉樹結構,是紅黑樹的存儲結構,用於紅黑樹中存儲數據
TreeBin 是封裝 TreeNode 的容器,提供轉換紅黑樹的一些條件和鎖的控制
③、存儲對象時(put() 方法):
initTable()
方法來進行初始化addCount()
方法統計 size,而且檢查是否須要擴容④、擴容方法 transfer()
:默認容量爲 16,擴容時,容量變爲原來的兩倍
helpTransfer()
:調用多個工做線程一塊兒幫助進行擴容,這樣的效率就會更高
⑤、獲取對象時(get()方法):
ForwardingNode.find()
方法,查找該結點,匹配就返回;1.7中程序運行時可以同時更新 ConccurentHashMap 且不產生鎖競爭的最大線程數。默認爲 16,且能夠在構造函數中設置。當用戶設置併發度時,ConcurrentHashMap 會使用大於等於該值的最小2冪指數做爲實際併發度(假如用戶設置併發度爲17,實際併發度則爲32)
1.8中併發度則無太大的實際意義了,主要用處就是當設置的初始容量小於併發度,將初始容量提高至併發度大小