1. 程式人生 > >HashMap、HashTable、LinkedHashMap的區別

HashMap、HashTable、LinkedHashMap的區別

HashMap、HashTable、LinkedHashMap的區別

HashSet:

HashSet其實就是具有相同value的HashMap
equals()相同,hashcode()也相同
hashcode()相同,equals()不一定相同

HashMap:

  1. 不是執行緒安全的,原始碼中沒有與執行緒有關的任何內容
  2. 有一個靜態內部類Node,實現了Map.Entry介面,該類中包含key、key的hash值、value、下一個node的引用,HashMap中的每一個元素都是一個Node物件
  3. 特殊的例項變數threshold:域值,指HashMap中的有效資料等於這個值的時候,就要進行擴容
  4. 特殊的例項變數loadFactor:預設為0.75,指每次進行擴容後的threshold與可容納的長度的比值
  5. 儲存資料時,是根據key的hash值作為陣列的下標來儲存,預設的初始化沒有對HashMap設定容量大小,當第一次put資料時,會設定預設容量=16,即一個長度為16的陣列,然後根據loadFactor得到域值(預設為16*0.75=12),即當陣列中的實際個數大於12時,將陣列的長度和域值一起進行擴容(<<1)為原來的兩倍
  6. 擴容後的數為2的冪次方,因為在put資料時,是根據hash值與長度求&(也就是取模)得到下標進行儲存的,即容量length為2^n,hash&(length-1)==hash%length

HashMap的容量為什麼必須為2的冪次

如 length=16(二進位制10000),hash1=10(二進位制1010),hash2=7(二進位制111)
分別將hash1和hash2與length進行位運算&:
length&hash1=0,length&hash2=0
由此推斷16以內有所有數與16進行&fct運算的結果都為0,顯然不對,但將16-1:
如 length=15(二進位制1111),hash1=10(二進位制1010),hash2=7(二進位制111)
分別將hash1和hash2與length進行位運算&:
length&hash1=10,length&hash2=7
這樣的話,位運算的結果是可以作為Hash的
因此HashMap的容量必須為2的冪次,元素所在的位置為容量-1與hash進行&運算

  1. 正常情況下,長度最大為1<<30(230),loadFactor的計算方法不變,如果實際值還是超過了230,則將域值和loadFactor都設定為Integer.MAX_VALUE
  2. 結構是陣列(hash表)+連結串列+紅黑數的實現,主要是陣列+連結串列

a). 如果key的hash值不相等,HashMap為陣列,陣列中的每個元素為Node
b). 如果key的hash值相等,則這些相等的元素為連結串列,但當這個連結串列的長度大於8的時候,將這個連結串列轉換成紅黑樹(紅黑樹為java8新加)
c). 8的來源是根據概率計算得來的,即出現hash值相等的個數很少會大於8,而紅黑樹大於8時,查詢成本低,但新增成本高,因為要進行左右旋。
9. 紅黑樹的查詢長度為log(n),當長度為8時,查詢為3,連結串列為折半查詢,當長度大於8時適用
10. 允許key和value為空

為什麼HashMap執行緒不安全

  1. 因為HashMap中的所有操作,都沒有任何有關執行緒的設定
  2. 主要是在擴容的時候,會建立新的空陣列並生成新的hash值來儲存資料,多執行緒會丟失資料
  3. 多執行緒情況下,將不能把每個hash值相同的元素儲存下來

當給HashMap指定初始大小後,其容量實際大小是多少

  1. tableSizeFor()這個方法很神奇,當指定初始大小後,會對這個數字進行-1、右移、或運算,來算出比這個數字大,且離這個數字最近的2的冪次數
  2. 第一步-1操作,目的是為了防止指定的數字本身就是一個2的冪次數字
  3. 第二步右移和或運算,即將第一步後的數字進行右移,右移位數為:1、2、4、8、16,然後與右移前的數字進行或運算,右移1位:因為一個數字的二進位制數,第一位必然是1,如右移前為0001xxxx,右移後為00001xxx,這兩個二進位制數進行或操作後結果為00011xxx,這樣會必然出現有兩個11,然後再右移2位和或,得到的是0001111x,依此類推,最後出現的數字為00011111,然後進行+1,得到的數字正好是一個2的冪次數

HashTable:

  1. 是執行緒安全的,synchronized關鍵字只新增到了方法上,所以是相對的執行緒安全
  2. 靜態內部類為Entry實現了Map.Entry
  3. 是陣列+連結串列的結構
  4. 預設的初始化大小為11,loadFactory為0.75,域值為11*0.75=8,且域值最大為Integer.MAX_VALUE-7,防止OOM
  5. 在擴容後,為原來的2倍+1
  6. value不允許為空
    7、 不常使用,因為效率沒有HashMap高,執行緒也不是絕對安全

LinkedHashMap

  1. 不是執行緒安全
  2. 繼承了HashMap
  3. 靜態內部類為Entry也是繼承自HashMap.Node,並多了兩個引用before、after,表明為雙向連結串列結構
  4. 整體結構為連結串列+陣列(hash表)+連結串列+紅黑樹,即除了與HashMap的結構相同外,還另外維護一個雙向連結串列結構,該連結串列中內容為每個節點的前一個和後一個節點
  5. 有一個成員屬性boolean accessOrder,表示迭代順序,預設為false(true為訪問順序,指根據訪問的順序進行排序;false為插入順序,指根據插入的順序進行排序)
  6. 增加(put):用的還是HashMap中的方法,在HashMap的put()方法中呼叫了newNode()方法,該方法在HashMap中直接返回新的Node,LinkedHashMap重寫了該方法,指定了Node的before和after,來保證有序
  7. 在HashMap中有幾個模板方法,實現是在LinkedHashMap中,實現內容即根據accessOrder對連結串列進行修改以保證對應順序
  8. 在進行containsValue時,是通過迴圈節點來實現,比HashMap通過下標迴圈取數快
  9. 其他特點與HashMap一樣,因為是繼承自HashMap
  10. 有一個特殊的演算法:LRU(least recently used)最近最少使用演算法,或距今最久未用淘汰演算法,是快取管理中的一個演算法,其內容為將最近訪問過的元素移動到鏈尾,鏈頭的元素即為訪問過時間最久的