Java集合源碼分析--HashMap
轉載自 http://www.cnblogs.com/zhangyinhua/p/7698642.html#_label0
一,關於HashMap API定義
1、哈希表基於map接口的實現,這個實現提供了map所有的操作,並且提供了key和value可以為null,(HashMap和HashTable大致上是一樣的除了hashmap是異步的和允許key和value為null),
這個類不確定map中元素的位置,特別要提的是,這個類也不確定元素的位置隨著時間會不會保持不變。
2.HashMap的實例有兩個參數影響性能,初始化容量(initialCapacity)和loadFactor加載因子。
二,HashMap 的屬性
HashMap的實例有兩個參數影響其性能。
初始容量:哈希表中桶的數量 加載因子:哈希表在其容量自動增加之前可以達到多滿的一種尺度
當哈希表中條目數超出了當前容量*加載因子(其實就是HashMap的實際容量)時,則對該哈希表進行rehash操作,將哈希表擴充至兩倍的桶數。
//Java中默認初始容量為16,加載因子為0.75。
HashMap hm=new HashMap(); for(int i=0;i<17;i++){ hm.put(i,i); } System.out.println(hm.size());//17 System.out.println(hm.table.length);//32
1)loadFactor加載因子
定義:loadFactor譯為裝載因子。裝載因子用來衡量HashMap滿的程度,是控制數組存放數據的疏密程度。計算HashMap的實時裝載因子的方法為:size/capacity,而不是占用桶的數量去除以capacity。
loadFactor越趨近於1,那麽數組中存放的數據(entry)也就越多,但我們在通過key拿到我們的value時,是先通過key的hashcode值,找到對應數組中的位置,
如果該位置中有很多元素,則需要通過equals來依次比較鏈表中的元素,拿到我們的value值,這樣花費的性能就很高,
loadFactor越趨近於0,那麽數組中存放的數據也就越稀,分散的太開,浪費很多空間,這樣也不好,
所以在hashMap中loadFactor的初始值就是0.75,一般情況下不需要更改它。
2)桶
數組中每一個位置上都放有一個桶,每個桶裏就是裝一個鏈表,鏈表中可以有很多個元素(entry),這就是桶的意思。也就相當於把元素都放在桶中。
3)capacity
capacity譯為容量代表的數組的容量,也就是數組的長度,同時也是HashMap中桶的個數。默認值是16。
4)size的含義
size就是在該HashMap的實例中實際存儲的元素的個數
5)threshold的作用
threshold = capacity * loadFactor,當Size>=threshold的時候,那麽就要考慮對數組的擴增了,也就是說,這個的意思就是衡量數組是否需要擴增的一個標準。
註意這裏說的是考慮,因為實際上要擴增數組,除了這個size>=threshold條件外,還需要另外一個條件。
什麽時候會擴增數組的大小?在put一個元素時先size>=threshold並且還要在對應數組位置上有元素,這才能擴增數組。
二、HashMap的源碼分析
2.1、HashMap的層次關系與繼承結構
1)HashMap繼承結構
上面就繼承了一個abstractMap,也就是用來減輕實現Map接口的編寫負擔。
2)實現接口
Map<K,V>:在AbstractMap抽象類中已經實現過的接口,這裏又實現,實際上是多余的。但每個集合都有這樣的錯誤,也沒過大影響
Cloneable:能夠使用Clone()方法,在HashMap中,實現的是淺層次拷貝,即對拷貝對象的改變會影響被拷貝的對象。
Serializable:能夠使之序列化,即可以將HashMap對象保存至本地,之後可以恢復狀態。
2.2、HashMap類的屬性
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 序列號 private static final long serialVersionUID = 362498820763181265L; // 默認的初始容量是16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; // 默認的填充因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 當桶(bucket)上的結點數大於這個值時會轉成紅黑樹 static final int TREEIFY_THRESHOLD = 8; // 當桶(bucket)上的結點數小於這個值時樹轉鏈表 static final int UNTREEIFY_THRESHOLD = 6; // 桶中結構轉化為紅黑樹對應的table的最小大小 static final int MIN_TREEIFY_CAPACITY = 64; // 存儲元素的數組,總是2的冪次倍 transient Node<k,v>[] table; // 存放具體元素的集 transient Set<map.entry<k,v>> entrySet; // 存放元素的個數,註意這個不等於數組的長度。 transient int size; // 每次擴容和更改map結構的計數器 transient int modCount; // 臨界值 當實際大小(容量*填充因子)超過臨界值時,會進行擴容 int threshold; // 填充因子 final float loadFactor; }
2.3、HashMap的構造方法
有四個構造方法,構造方法的作用就是記錄一下16這個數給threshold(這個數值最終會當作第一次數組的長度。)和初始化加載因子。註意,hashMap中table數組一開始就已經是個沒有長度的數組了。
構造方法中,並沒有初始化數組的大小,數組在一開始就已經被創建了,構造方法只做兩件事情,一個是初始化加載因子,另一個是用threshold記錄下數組初始化的大小。註意是記錄。
三、HashMap源碼分析(二)
3.1、put方法
/*
* 1. 通過key的hash值確定table下標
* 2. 查找table下標,如果key存在則更新對應的value
* 3. 如果key不存在則調用addEntry()方法
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 哈希碼相同並且對象相同時
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
// 新值替換舊值,並返回舊值
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// key不存在時,加入新元素
modCount++;
addEntry(hash, key, value, i);
return null;
}
3.2、get方法
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
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 != null && key.equals(k))))
return e;
}
return null;
}
3.3、resize方法
/** * 擴展到指定的大小 */ void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
四、總結
4.1、關於數組擴容
從putVal源代碼中我們可以知道,當插入一個元素的時候size就加1,若size大於threshold的時候,就會進行擴容。假設我們的capacity大小為32,loadFator為0.75,則threshold為24 = 32 * 0.75,
此時,插入了25個元素,並且插入的這25個元素都在同一個桶中,桶中的數據結構為紅黑樹,則還有31個桶是空的,也會進行擴容處理,其實,此時,還有31個桶是空的,好像似乎不需要進行擴容處理,
但是是需要擴容處理的,因為此時我們的capacity大小可能不適當。我們前面知道,擴容處理會遍歷所有的元素,時間復雜度很高;前面我們還知道,經過一次擴容處理後,元素會更加均勻的分布在各個桶中,
會提升訪問效率。所以,說盡量避免進行擴容處理,也就意味著,遍歷元素所帶來的壞處大於元素在桶中均勻分布所帶來的好處。
4.2、總結
1)要知道hashMap在JDK1.8以前是一個鏈表散列這樣一個數據結構,而在JDK1.8以後是一個數組加鏈表加紅黑樹的數據結構。
2)通過源碼的學習,hashMap是一個能快速通過key獲取到value值得一個集合,原因是內部使用的是hash查找值得方法。
Java集合源碼分析--HashMap