HashMap、HashTable、ConcurrentHashMap、HashSet區別 線程安全類

Hash,Tree數據結構時間複雜度分析:HashMap, HashTable,HashSet,TreeMap 的時間複雜度    程序員

總結:

1. ConcurrentHashMap 與HashMap和Hashtable 最大的不一樣在於:put和 get 兩次Hash到達指定的HashEntry,github

第一次hash到達Segment,第二次到達Segment裏面的Entry,而後在遍歷entry鏈表面試

2:HashSet底層採用的是HashMap進行實現的,可是沒有key-value,只有HashMap的key set的視圖,HashSet不允許重複的對象算法

3:Hashtable是基於Dictionary類的,而HashMap是基於Map接口的一個實現數組

4:Hashtable裏默認的方法是同步的,而HashMap則是非同步的,所以Hashtable是多線程安全的緩存

5:HashMap能夠將空值做爲一個表的條目的key或者value,HashMap中因爲鍵不能重複,所以只有一條記錄的Key能夠是空值,安全

而value能夠有多個爲空,但HashTable不容許null值(鍵與值均不行)

6:內存初始大小不一樣,HashTable初始大小是11,而HashMap初始大小是16,concurrentHashMap的鏈表超過8個數據以後自動變成紅黑樹

7:內存擴容時採起的方式也不一樣,Hashtable採用的是2*old+1,而HashMap是2*old。

①HashMap的工做原理

HashMap實現了Map接口,Map接口對鍵值對進行映射。Map中不容許重複的鍵。Map接口有兩個基本的實現,HashMap和TreeMap。TreeMap保存了對象的排列次序,而HashMap則不能。HashMap容許鍵和值爲null。HashMap是非synchronized的,但collection框架提供方法能保證HashMap synchronized,這樣多個線程同時訪問HashMap時,能保證只有一個線程更改Map。

public Object put(Object Key,Object value) 方法用來將元素添加到map中。

HashMap基於hashing原理,咱們經過put()和get()方法儲存和獲取對象。當咱們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,而後找到bucket位置來儲存值對象。當獲取對象時,經過鍵對象的equals()方法找到正確的鍵值對,而後返回值對象。HashMap使用鏈表來解決碰撞問題,當發生碰撞了,對象將會儲存在鏈表的下一個節點中。 HashMap在每一個鏈表節點中儲存鍵值對對象。當兩個不一樣的鍵對象的hashcode相同時會發生什麼? 它們會儲存在同一個bucket位置的鏈表中。鍵對象的equals()方法用來找到鍵值對。 

一、HashMap和Hashtable的區別   

HashMap 支持key=null 可是 Hashtable 不支持 key=null ,concurrentHashMap 也不能夠爲空

HashTable是Java中的遺留類,如今不怎麼用了。也許HashTable類的設計者當時認爲null做爲key和value是沒有什麼用的。 
HashMap是以後的版本引進的類,它的接口Map表達的意義更爲普遍,也許HashMap的設計者認爲null做爲key和value是有實際意義的,因此才容許爲null。 
固然實際項目中,真的是有value爲null的狀況的。key爲null的狀況比較少見,但不表明沒有。HashMap容許null爲key和value應當是類的設計者思考讓這個類更有用的設計吧

HashMap和HashTable都實現了Map接口,但決定用哪個以前先要弄清楚它們之間的分別。 主要的區別有:線程安全性,同步(synchronization),以及速度

        一、Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現。

  1. HashMap幾乎能夠等價於Hashtable,除了HashMap是非synchronized的,並能夠接受null(HashMap能夠接受爲null的鍵值(key)和值(value),而Hashtable則不行)。
  2. HashMap是非synchronized,而Hashtable是synchronized,這意味着Hashtable是線程安全的,多個線程能夠共享一個Hashtable;而若是沒有正確的同步的話,多個線程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。
  3. 另外一個區別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。因此當有其它線程改變了HashMap的結構(增長或者移除元素),將會拋出ConcurrentModificationException,但迭代器自己的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這並非一個必定發生的行爲,要看JVM。這條一樣也是Enumeration和Iterator的區別。
  4. 注意 fail-fast迭代器和enumerator迭代器的區別致使了hashmap和hashtable 遍歷的不一樣 參考:Iterator和Enumeration的區別
  5. 因爲Hashtable是線程安全的也是synchronized,因此在單線程環境下它比HashMap要慢。若是你不須要同步,只須要單一線程,那麼使用HashMap性能要好過Hashtable。
  6. HashMap不能保證隨着時間的推移Map中的元素次序是不變的。

要注意的一些重要術語:

1) sychronized意味着在一次僅有一個線程可以更改Hashtable。就是說任何線程要更新Hashtable時要首先得到同步鎖,其它線程要等到同步鎖被釋放以後才能再次得到同步鎖更新Hashtable。

2) Fail-safe和iterator迭代器相關。若是某個集合對象建立了Iterator或者ListIterator,而後其它的線程試圖「結構上」更改集合對象,將會拋出ConcurrentModificationException異常。但其它線程能夠經過set()方法更改集合對象是容許的,由於這並無從「結構上」更改集合。可是假如已經從結構上進行了更改,再調用set()方法,將會拋出IllegalArgumentException異常。

3) 結構上的更改指的是刪除或者插入一個元素,這樣會影響到map的結構。

咱們可否讓HashMap同步?

HashMap能夠經過下面的語句進行同步:

Map m = Collections.synchronizeMap(hashMap);

僅在你須要徹底的線程安全的時候使用Hashtable,而若是你使用Java 5或以上的話,請使用ConcurrentHashMap吧。

HashMap和HashSet都是collection框架的一部分,它們讓咱們可以使用對象的集合。collection框架有本身的接口和實現,主要分爲Set接口,List接口和Queue接口。Set的集合裏不容許對象有重複的值,List容許有重複,它對集合中的對象進行索引,Queue的工做原理是FCFS算法(First Come, First Serve)。

二、HashMap和HashSet的區別

什麼是HashSet 底層是HashMap實現的,看源碼:

   public HashSet() { map = new HashMap<>();  }

 

HashSet實現了Set接口,它不容許集合中有重複的值,當咱們提到HashSet時,第一件事情就是在將對象存儲在HashSet以前,要先確保對象重寫equals()和hashCode()方法,這樣才能比較對象的值是否相等,以確保set中沒有儲存相等的對象。若是咱們沒有重寫這兩個方法,將會使用這個方法的默認實現。

public boolean add(Object o)方法用來在Set中添加元素,當元素值重複時則會當即返回false,若是成功添加的話會返回true。

HashSet 就是HashMap的key列,HashSet 能夠爲空,HashMap的key和Value均可覺得空,TreeMap 不容許爲空,由於treeMap須要排序

 

*HashMap* *HashSet*
HashMap實現了Map接口 HashSet實現了Set接口
HashMap儲存鍵值對 HashSet僅僅存儲對象
使用put()方法將元素放入map中 使用add()方法將元素放入set中
HashMap中使用鍵對象來計算hashcode值

HashSet使用成員對象來計算hashcode值,對於兩個對象來講hashcode可能相同,

因此equals()方法用來判斷對象的相等性,若是兩個對象不一樣的話,那麼返回false

HashMap比較快,由於是使用惟一的鍵來獲取對象 HashSet較HashMap來講比較慢

 


 

 

 

 

 

「你用過HashMap嗎?」 「什麼是HashMap?你爲何用到它?」  

HashMap能夠接受null鍵值和值,而Hashtable則不能;HashMap是非synchronized;HashMap很快;以及HashMap儲存的是鍵值對等等。這顯示出你已經用過HashMap,並且對它至關的熟悉。 

「你知道HashMap的工做原理嗎?」 「你知道HashMap的get()方法的工做原理嗎?」

你也許會回答「我沒有詳查標準的Java API,你能夠看看Java源代碼或者Open JDK。」「我能夠用Google找到答案。」

但一些面試者可能能夠給出答案,「HashMap是基於hashing的原理,咱們使用put(key, value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。當咱們給put()方法傳遞鍵和值時,咱們先對鍵調用hashCode()方法,返回的hashCode用於找到bucket位置來儲存Entry對象。」這裏關鍵點在於指出,HashMap是在bucket中儲存鍵對象和值對象,做爲Map.Entry。這一點有助於理解獲取對象的邏輯。若是你沒有意識到這一點,或者錯誤的認爲僅僅只在bucket中存儲值的話,你將不會回答如何從HashMap中獲取對象的邏輯。這個答案至關的正確,也顯示出面試者確實知道hashing以及HashMap的工做原理。可是這僅僅是故事的開始,當面試官加入一些Java程序員天天要碰到的實際場景的時候,錯誤的答案頻現。下個問題多是關於HashMap中的碰撞探測(collision detection)以及碰撞的解決方法:

「當兩個對象的hashcode相同會發生什麼?」 從這裏開始,真正的困惑開始了,一些面試者會回答由於hashcode相同,因此兩個對象是相等的,HashMap將會拋出異常,或者不會存儲它們。而後面試官可能會提醒他們有equals()和hashCode()兩個方法,並告訴他們兩個對象就算hashcode相同,可是它們可能並不相等。一些面試者可能就此放棄,而另一些還能繼續挺進,他們回答「由於hashcode相同,因此它們的bucket位置相同,‘碰撞’會發生。由於HashMap使用鏈表存儲對象,這個Entry(包含有鍵值對的Map.Entry對象)會存儲在鏈表中。」這個答案很是的合理,雖然有不少種處理碰撞的方法,這種方法是最簡單的,也正是HashMap的處理方法。但故事尚未完結,面試官會繼續問:

「若是兩個鍵的hashcode相同,你如何獲取值對象?」 面試者會回答:當咱們調用get()方法,HashMap會使用鍵對象的hashcode找到bucket位置,而後獲取值對象。面試官提醒他若是有兩個值對象儲存在同一個bucket,他給出答案:將會遍歷鏈表直到找到值對象。面試官會問由於你並無值對象去比較,你是如何肯定肯定找到值對象的?除非面試者直到HashMap在鏈表中存儲的是鍵值對,不然他們不可能回答出這一題。

其中一些記得這個重要知識點的面試者會說,找到bucket位置以後,會調用keys.equals()方法去找到鏈表中正確的節點,最終找到要找的值對象。完美的答案!

許多狀況下,面試者會在這個環節中出錯,由於他們混淆了hashCode()和equals()方法。由於在此以前hashCode()屢屢出現,而equals()方法僅僅在獲取值對象的時候纔出現。一些優秀的開發者會指出使用不可變的、聲明做final的對象,而且採用合適的equals()和hashCode()方法的話,將會減小碰撞的發生,提升效率。不可變性使得可以緩存不一樣鍵的hashcode,這將提升整個獲取對象的速度,使用String,Interger這樣的wrapper類做爲鍵是很是好的選擇。

若是你認爲到這裏已經完結了,那麼聽到下面這個問題的時候,你會大吃一驚。「若是HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦?」除非你真正知道HashMap的工做原理,不然你將回答不出這道題。默認的負載因子大小爲0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)同樣,將會建立原來HashMap大小的兩倍的bucket數組,來從新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫做rehashing,由於它調用hash方法找到新的bucket位置。

若是你可以回答這道問題,下面的問題來了:「你瞭解從新調整HashMap大小存在什麼問題嗎?」你可能回答不上來,這時面試官會提醒你當多線程的狀況下,可能產生條件競爭(race condition)。

當從新調整HashMap大小的時候,確實存在條件競爭,由於若是兩個線程都發現HashMap須要從新調整大小了,它們會同時試着調整大小。在調整大小的過程當中,存儲在鏈表中的元素的次序會反過來,由於移動到新的bucket位置的時候,HashMap並不會將元素放在鏈表的尾部,而是放在頭部,這是爲了不尾部遍歷(tail traversing)。若是條件競爭發生了,那麼就死循環了。這個時候,你能夠質問面試官,爲何這麼奇怪,要在多線程的環境下使用HashMap呢?:)

熱心的讀者貢獻了更多的關於HashMap的問題:

  1. 爲何String, Interger這樣的wrapper類適合做爲鍵? String, Interger這樣的wrapper類做爲HashMap的鍵是再適合不過了,並且String最爲經常使用。由於String是不可變的,也是final的,並且已經重寫了equals()和hashCode()方法了。其餘的wrapper類也有這個特色。不可變性是必要的,由於爲了要計算hashCode(),就要防止鍵值改變,若是鍵值在放入時和獲取時返回不一樣的hashcode的話,那麼就不能從HashMap中找到你想要的對象。不可變性還有其餘的優勢如線程安全。若是你能夠僅僅經過將某個field聲明成final就能保證hashCode是不變的,那麼請這麼作吧。由於獲取對象的時候要用到equals()和hashCode()方法,那麼鍵對象正確的重寫這兩個方法是很是重要的。若是兩個不相等的對象返回不一樣的hashcode的話,那麼碰撞的概率就會小些,這樣就能提升HashMap的性能。
  2. 咱們可使用自定義的對象做爲鍵嗎? 這是前一個問題的延伸。固然你可能使用任何對象做爲鍵,只要它遵照了equals()和hashCode()方法的定義規則,而且當對象插入到Map中以後將不會再改變了。若是這個自定義對象時不可變的,那麼它已經知足了做爲鍵的條件,由於當它建立以後就已經不能改變了。
  3. 咱們可使用CocurrentHashMap來代替Hashtable嗎?這是另一個很熱門的面試題,由於ConcurrentHashMap愈來愈多人用了。咱們知道Hashtable是synchronized的,可是ConcurrentHashMap同步性能更好,由於它僅僅根據同步級別對map的一部分進行上鎖。ConcurrentHashMap固然能夠代替HashTable,可是HashTable提供更強的線程安全性。看看這篇博客查看Hashtable和ConcurrentHashMap的區別。

我我的很喜歡這個問題,由於這個問題的深度和廣度,也不直接的涉及到不一樣的概念。讓咱們再來看看這些問題設計哪些知識點:

hashing的概念
HashMap中解決碰撞的方法
equals()和hashCode()的應用,以及它們在HashMap中的重要性
不可變對象的好處
HashMap多線程的條件競爭
從新調整HashMap的大小

 2、HashMap與HashTable的區別:

一、 繼承和實現區別

  Hashtable是基於陳舊的Dictionary類,完成了Map接口;HashMap是Java 1.2引進的Map接口的一個實現(HashMap繼承於AbstractMap,AbstractMap完成了Map接口)。

Hashtable: 源碼: 

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {
....
}

HashMap 源碼: 

  public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
....

}

 另外:HashSet 繼承於AbstractSet 

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
.....
}

 二、 線程安全不一樣

  HashTable的方法是同步的,HashMap是未同步,因此在多線程場合要手動同步HashMap。

  三、 對null的處理不一樣

  HashTable不容許null值(key和value都不能夠),HashMap容許null值(key和value均可以)。concurrentHashMap 也不能夠爲空 ,即 HashTable不容許null值其實在編譯期不會有任何的不同,會照樣執行,只是在運行期的時候Hashtable中設置的話回出現空指針異常。 HashMap容許null值是指能夠有一個或多個鍵所對應的值爲null。當get()方法返回null值時,便可以表示 HashMap中沒有該鍵,也能夠表示該鍵所對應的值爲null。

所以,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵,而應該用containsKey()方法來判斷。

  四、 方法不一樣

  HashTable有一個contains(Object value),功能和containsValue(Object value)功能同樣。

  五、HashTable使用Enumeration,HashMap使用Iterator。(網友看看:我很奇怪, 我用Iterator也能夠遍歷HashTable啊,參照:HashTable的五種遍歷方式

)

  六、HashTable中hash數組默認大小是11,增長的方式是 old*2+1。HashMap中hash數組的默認大小是16,並且必定是2的指數。

  七、哈希值的使用不一樣,HashTable直接使用對象的hashCode,代碼是這樣的:  

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

  而HashMap從新計算hash值,並且用與代替求模:  

 int hash = hash(k);
 int i = indexFor(hash, table.length);
  static int hash(Object x) {
  int h = x.hashCode();
  h += ~(h << 9);
  h ^= (h >>> 14);
  h += (h << 4);
  h ^= (h >>> 10);
  return h;
  }
  static int indexFor(int h, int length) {
  return h & (length-1);
  }

 

區別

Hashtable

Hashmap

繼承、實現

Hashtable extends Dictionary implements Map, Cloneable,Serializable

HashMap extends AbstractMap implements Map, Cloneable,Serializable

線程同步

已經同步過的能夠安全使用

未同步的,可使用Colletcions進行同步MapCollections.synchronizedMap(Mapm)

對null的處理

 

Hashtable table = new Hashtable();

table.put(null, "Null");

table.put("Null", null);

table.contains(null);

table.containsKey(null);

table.containsValue(null);

後面的5句話在編譯的時候不會有異常,可在運行的時候會報空指針異常具體緣由能夠查看源代碼

public synchronized V put(K key, V value) {

// Make sure the value is not null

if (value == null) {

throw new NullPointerException();

}

HashMap map = new HashMap();
map.put(null, "Null");

map.put("Null", null);

map.containsKey(null);

map.containsValue(null);

以上這5條語句不管在編譯期,仍是在運行期都是沒有錯誤的.

在HashMap中,null能夠做爲鍵,這樣的鍵只有一個;能夠有一個或多個鍵所對應的值爲null。當get()方法返回null值時,便可以表示 HashMap中沒有該鍵,也能夠表示該鍵所對應的值爲null。所以,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵,而應該用containsKey()方法來判斷。

增加率

protected void rehash() {

int oldCapacity = table.length;

Entry[] oldMap = table;

int newCapacity = oldCapacity * 2 + 1;

Entry[] newMap = new Entry[newCapacity];

modCount++;

threshold = (int)(newCapacity * loadFactor);

table = newMap;

for (int i = oldCapacity ; i-- > 0 ;) {

for (Entryold = oldMap[i] ; old != null ; ) {

Entrye = old;

old = old.next;

int index = (e.hash & 0x7FFFFFFF) % newCapacity;

e.next = newMap[index];

newMap[index] = e;

}}}

void addEntry(int hash, K key, V value, int bucketIndex) {

Entrye = table[bucketIndex];

table[bucketIndex] = new Entry(hash, key, value, e);

if (size++ >= threshold)

resize(2 * table.length);

}

 

哈希值的使用

HashTable直接使用對象的hashCode,代碼是這樣的:

public synchronizedbooleancontainsKey(Object key) {

Entry tab[] = table;

int hash = key.hashCode();

int index = (hash & 0x7FFFFFFF) % tab.length;

for (Entrye = tab[index] ; e != null ; e = e.next) {

if ((e.hash == hash) && e.key.equals(key)) {

return true;

}}

return false;}

HashMap從新計算hash值,並且用與代替求模

public boolean containsKey(Object key) {

Object k = maskNull(key);

int hash = hash(k.hashCode());

int i = indexFor(hash, table.length);

Entry e = table[i];

while (e != null) {

if (e.hash == hash && eq(k, e.key))

return true;

e = e.next;

}

return false;

}

   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

8:哈希值的計算方法不一樣,Hashtable直接使用的是對象的hashCode,而HashMap則是在對象的hashCode的基礎上還進行了一些變化

源代碼分析: 

private transient HashMap<E,Object> map;  
private static final Object PRESENT = new Object();    
 public HashSet() {  
    map = new HashMap<E,Object>();  
    }  

從上面的代碼中得出的結論是HashSet的確是採用HashMap來實現的,並且每個鍵都關鍵同一個Object類的對象,所以鍵所關聯的值沒有意義,真正有意義的是鍵。而HashMap裏的鍵是不容許重複的,

 3、HashMap和TreeMap區別

一、HashMap是基於散列表實現的,時間複雜度平均能達到O(1)。

    TreeMap基於紅黑樹(一種自平衡二叉查找樹)實現的,時間複雜度平均能達到O(log n)。
二、HashMap、TreeMap都繼承AbstractMap抽象類;TreeMap實現SortedMap接口,因此TreeMap是有序的!HashMap是無序的。    接口層次:

    public interface SortedMap<K,V> extends Map<K,V>
    public interface NavigableMap<K,V> extends SortedMap<K,V>
    public class HashMap<K,V>     extends AbstractMap<K,V>    implements Map<K,V>, Cloneable, Serializable
    public class HashMap<K,V>    extends AbstractMap<K,V>    implements Map<K,V>, Cloneable, Serializable

 三、兩種常規Map性能

    HashMap:適用於在Map中插入、刪除和定位元素。
    Treemap:適用於按天然順序或自定義順序遍歷鍵(key)。    
4.總結:HashMap一般比TreeMap快一點(樹和哈希表的數據結構使然),建議多使用HashMap,在須要排序的Map時候才用TreeMap。 

3、HashSet原理 

     HashSet實現Set接口,由哈希表(其實是一個value永遠爲null的HashMap實例)支持。它不保證set 的迭代順序;特別是它不保證該順序恆久不變。此類容許使用null元素。

     底層是HashMap,HashSet底層是採用HashMap實現的。HashSet 的實現比較簡單,HashSet 的絕大部分方法都是經過調用 HashMap 的方法來實現的,所以 HashSet 和 HashMap 兩個集合在實現本質上是相同的。

    TeeSet是經過TreeMap實現的,只不過Set用的只是Map的key Map的key和Set都有一個共同的特性就是集合的惟一性.TreeMap更是多了一個排序的功能. hashCode和equal()是HashMap用的,由於無需排序因此只須要關注定位和惟一性便可. 

   a.hashCode是用來計算hash值的,hash值是用來肯定hash表索引的.  

  b.hash表

  •  Map的key和Set都有一個共同的特性就是集合的惟一性.TreeMap更是多了一個排序的功能.
  •  hashCode和equal()是HashMap用的, 由於無需排序因此只須要關注定位和惟一性便可.
       a. hashCode是用來計算hash值的,hash值是用來肯定hash表索引的.
       b. hash表中的一個索引處存放的是一張鏈表, 因此還要經過equal方法循環比較鏈上的每個對象才能夠真正定位到鍵值對應的Entry.
       c. put時,若是hash表中沒定位到,就在鏈表前加一個Entry,若是定位到了,則更換Entry中的value,並返回舊value
  • 因爲TreeMap須要排序,因此須要一個Comparator爲鍵值進行大小比較.固然也是用Comparator定位的.
       a. Comparator能夠在建立TreeMap時指定
       b. 若是建立時沒有肯定,那麼就會使用key.compareTo()方法,這就要求key必須實現Comparable接口.
       c. TreeMap是使用Tree數據結構實現的,因此使用compare接口就能夠完成定位了.  

     Set的實現類的集合對象中不可以有重複元素,HashSet也同樣他是使用了一種標識來肯定元素的不重複,HashSet用一種算法來保證HashSet中的元素是不重複的,   HashSet採用哈希算法,底層用數組存儲數據。默認初始化容量16,加載因子0.75
     Object類中的hashCode()的方法是全部子類都會繼承這個方法,這個方法會用Hash算法算出一個Hash(哈希)碼值返回,HashSet會用Hash碼值去和數組長度取模, 模(這個模就是對象要存放在數組中的位置)相同時纔會判斷數組中的元素和要加入的對象的內容是否相同,若是不一樣纔會添加進去。
     Hash算法是一種散列算法。

  Set hs=new HashSet();
 
  hs.add(o);
     |
         o.hashCode();
     |
  o%當前總容量  (0--15)
     |             
     |                 不發生衝突
        是否發生衝突-----------------直接存放
     |
     | 發生衝突
     |                  假(不相等)
        o1.equals(o2)-------------------找一個空位添加
     |
     |  是(相等)
         不添加

       覆蓋hashCode()方法的原則:

       一、必定要讓那些咱們認爲相同的對象返回相同的hashCode值
       二、儘可能讓那些咱們認爲不一樣的對象返回不一樣的hashCode值,不然,就會增長衝突的機率。
       三、儘可能的讓hashCode值散列開(兩值用異或運算可以使結果的範圍更廣)

       HashSet 的實現比較簡單,相關HashSet的操做,基本上都是直接調用底層HashMap的相關方法來完成,咱們應該爲保存到HashSet中的對象覆蓋hashCode()和equals(),由於再將對象加入到HashSet中時,會首先調用hashCode方法計算出對象的hash值,接着根據此hash值調用HashMap中的hash方法,獲得的值& (length-1)獲得該對象在hashMap的transient Entry[] table中的保存位置的索引,接着找到數組中該索引位置保存的對象,並調用equals方法比較這兩個對象是否相等,若是相等則不添加,注意:因此要存入HashSet的集合對象中的自定義類必須覆蓋hashCode(),equals()兩個方法,才能保證集合中元素不重複。在覆蓋equals()和hashCode()方法時, 要使相同對象的hashCode()方法返回相同值,覆蓋equals()方法再判斷其內容。爲了保證效率,因此在覆蓋hashCode()方法時, 也要儘可能使不一樣對象儘可能返回不一樣的Hash碼值。

 若是數組中的元素和要加入的對象的hashCode()返回了相同的Hash值(相同對象),纔會用equals()方法來判斷兩個對象的內容是否相同。  

public class HashSet<E> extends AbstractSet<E>  implements Set<E>, Cloneable, java.io.Serializable  
{  
    static final long serialVersionUID = -5024744406713321676L;  
  
    private transient HashMap<E,Object> map;  
  
    private static final Object PRESENT = new Object();  
  
    public HashSet() {  
        map = new HashMap<>();  
    }  
    public HashSet(Collection<? extends E> c) {  
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));  
        addAll(c);  
    }  
    public boolean add(E e) {  
        return map.put(e, PRESENT)==null;  
    }  
    public boolean remove(Object o) {  
        return map.remove(o)==PRESENT;  
    }  
    .......  
}  

    

4、ConcurrentMap 

      ConcurrentHashMap 表現區別:不能夠有null鍵,線程安全,原子操做。一個ConcurrentHashMap 由多個segment 組成,每一個segment 包含一個Entity 的數組。這裏比HashMap 多了一個segment 類。該類繼承了ReentrantLock 類,因此自己是一個鎖。當多線程對ConcurrentHashMap 操做時,不是徹底鎖住map, 而是鎖住相應的segment 。這樣提升了併發效率。缺點:當遍歷ConcurrentMap中的元素時,須要獲取全部的segment 的鎖,使用遍歷時慢。鎖的增多,佔用了系統的資源。使得對整個集合進行操做的一些方法 

5、LinkedHashMap是HashMap的一個子類

      LinkedHashMap保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先獲得的記錄確定是先插入的.也能夠在構造時用帶參數,按照應用次數排序。在遍歷的時候會比HashMap慢,不過有種狀況例外,當HashMap容量很大,實際數據較少時,遍歷起來可能會比LinkedHashMap慢,由於LinkedHashMap的遍歷速度只和實際數據有關,和容量無關,而HashMap的遍歷速度和他的容量有關。參考: ConcurrentHashMap原理分析 

6、java爲數據結構中的映射定義了一個接口java.util.Map;它有四個實現類,分別是HashMap Hashtable LinkedHashMap 和TreeMap.

由於TreeSet 不能爲空,可是HashSet能夠爲空,由於TreeSet須要排序,HashSet不須要,空的沒法排序

參考:HashMap底層實現原理/HashMap與HashTable區別/HashMap與HashSet區別

相關文章
相關標籤/搜索