1、HashMap的數據結構(HashMap通過hashcode對其內容進行高速查找,是無序的)
數據結構中有數組和鏈表來實現對數據的存儲,但這兩者基本上是兩個極端。
數組 :數組的存儲區是連續的,占用內存嚴重,故空間復雜度非常大。但數組的二分查找時間度小;數組的特點:尋址easy,插入和
刪除困難。
鏈表 :鏈表的儲存區離散。占用內存比較寬松。故空間復雜度非常小,但時間復雜度大;鏈表的特點:尋址困難,插入和刪除easy。哈希表
HashMap是由數組+鏈表組成。尋址easy,插入和刪除easy。(存儲單元數組Entry[],數組里面包括鏈表)
HashMap事實上也是由一個線性的數組實現的。
所以能夠理解為其存儲數據的容器就是一個線性容器;
HashMap里面有一個內部靜態類Entry,其重要的屬性有key,value,next,從屬性key,value 就能夠非常明顯的看出來 Entry就是
HashMap鍵值對實現的一個基礎bean;也就是說HashMap的基礎就是一個線性數組,這個數組就是Entry[]。Map里面的內容都保存
在Entry[]中;
/** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry[] table;
2、HashMap的存取實現
這里HashMap用了一個算法。
//存儲時候:
int hash=key.hashCode(); //獲取key的hashCode,這個值是一個固定的int值
int index=hash%Entry[].length。//獲取數組下標:key的hash值對Entry數組長度進行取余
Entry[index]=value。注意:假設兩個key通過hash%Entry[].length得到的index同樣。會不會覆蓋?
是不會的。Entry類有一個next屬性,作用是指向下一個Entry。打個例如, 第一個鍵值對A進來。通過計算其key的hash得到的
index=0。記做:Entry[0] = A。一會后又進來一個鍵值對B,通過計算其index也等於0,如今怎么辦?HashMap會這樣做:B.next =
A,Entry[0] = B,假設又進來C,index也等於0,那么C.next = B,Entry[0] = C;這樣我們發現index=0的地方事實上存取了A,B,C三個鍵值對,他
們通過next這個屬性鏈接在一起。
所以疑問不用操心。
也就是說Entry[]數組中存儲的是最后插入的數據
public V put(K key, V value) { if (key == null) return putForNullKey(value); //null總是放在數組的第一個鏈表中 int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); //遍歷鏈表 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //假設key在鏈表中已存在,則替換為新value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //參數e, 是Entry.next //假設size超過threshold,則擴充table大小。再散列 if (size++ >= threshold) resize(2 * table.length); }
2.2:取值
獲取key的hashcode指,通過hash值去hash%Entry[].length 獲取Entry[hash%Entry[].length],定位到該數組元素之后,再遍歷該元
素處的鏈表。
//取值時候:int hash=key.hashCode();
int index =hash%Entry[].length;
return Entry[index];
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); //先定位到數組元素。再遍歷該元素處的鏈表 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
當哈希表的容量超過默認容量時,必需要調整table的大小。
當容量達到最大值時,該方法Integer.MAX_VALUE返回。這時。就需要創建
一張表,將原來的表映射到新表中。
3、HashMap、HashTable和ConcurrentHashMap的線程安全問題
HashMap:線程不安全的。
HashTable:鎖住整張hash表,讓線程獨占。hashMap同意為空。
通過分析Hashtable就知道,synchronized是針對整張Hash表的,即每次鎖住整張表
讓線程獨占。安全的背后是巨大的浪費。
ConcurrentHashMap:一個更快的hashmap,它提供了好得多的並發性。多個讀操作差點兒總能夠並發地運行。
他是鎖段(默認:把hash表分為16個
段),在get,put,remove等操作中,ConcurrentHashMap僅僅鎖定當前須要用到的段,僅僅有在求size的時候才鎖定整張hash表。