在分析HashMap的源碼以前,先看一下HashMap在數據結構中的位置,常見的數據結構按照邏輯結構跟存儲結構能夠作以下劃分:
數組
先看一下源碼中的註釋安全
從註釋中能夠看出,HashMap是非線程安全的,而且容許Key跟Value爲空,同時也知道HashMap不是傳統意義上的鏈表或者數組,實質上是一個鏈表數組bash
前面分析過ArrayList跟LinkedList,各有利弊,可是實際上咱們在進行數據操做的時候但願查找跟修改效率都高起來,那麼它們倆實際上都不符合咱們的預期,因此就有了HashMap這種數據結構,下面看一下HashMap的繼承關係。數據結構
這個比較清晰,沒什麼好說的,如今分析一下HashMap的內部結構併發
//默認的初始化容量,必須是2的冪,默認爲4
static final int DEFAULT_INITIAL_CAPACITY = 4;
//最大容量,2的冪,而且小於1 << 30
static final int MAXIMUM_CAPACITY = 1 << 30;
//默認的負載因子,當構造方法中沒有指定負載因子的時候
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//一個空的HashMapEntry數組,table爲空的時候進行賦值
static final HashMapEntry<?,?>[] EMPTY_TABLE = {};
//HashMapEntry數組。長度必須是2的冪
transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;
//Map中Key——Value的對數
transient int size;
//閾值,size超過這個值就會進行擴容
int threshold;
//哈希表實際的負載因子
final float loadFactor = DEFAULT_LOAD_FACTOR;
//哈希表被修改的次數
transient int modCount;複製代碼
下面看一下這個內部類ide
static class HashMapEntry<K,V> implements Map.Entry<K,V> {
final K key;//Key值
V value;//Value值
HashMapEntry<K,V> next;//指向下一個HashMapEntry的指針
int hash;//Key的hash值
/**
* Creates new entry.
*/
HashMapEntry(int h, K k, V v, HashMapEntry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}複製代碼
在成員變量中有一個HashMapEntry數組,而此時的HashMapEntry中包含有指針,說明這個數組中的元素是鏈表,這樣一來就比較好理解了,HashMap的底層是一個數組,數組中的元素是一個鏈表,也就是一般所說的鏈表數組。ui
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}複製代碼
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}複製代碼
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}複製代碼
public HashMap(int initialCapacity, float loadFactor) {
//檢查初始化容量是否合乎規範
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
} else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {
initialCapacity = DEFAULT_INITIAL_CAPACITY;
}
//檢查負載因子是否合乎規範
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +loadFactor);
threshold = initialCapacity;//初始化的時候閾值是默認跟容量相同,當size改變的時候會從新賦值
init();//空實現
}複製代碼
void init() { }複製代碼
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);//首次添加元素,擴容
}
//Key爲空時,單獨處理
if (key == null)
return putForNullKey(value);
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
for (HashMapEntry<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;
}
}
modCount++;
addEntry(hash, key, value, i);//增長一個entry元素
return null;
}複製代碼
根據Hash值與table的size來計算entry在數組中的位置this
static int indexFor(int h, int length) {
return h & (length-1);
}複製代碼
首次設置table的容量spa
private void inflateTable(int toSize) {
// 若是toSize不是2的冪,那麼久將其轉化成值最相近的2的冪
int capacity = roundUpToPowerOf2(toSize);
//計算閾值
float thresholdFloat = capacity * loadFactor;
if (thresholdFloat > MAXIMUM_CAPACITY + 1) {
thresholdFloat = MAXIMUM_CAPACITY + 1;
}
//threshold 從新賦值
threshold = (int) thresholdFloat;
//自定義容量初始化table
table = new HashMapEntry[capacity];
}複製代碼
//處理key爲空的狀況線程
private V putForNullKey(V value) {
for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);//增長一個entry元素,key爲null的時候hash值設置爲0
return null;
}複製代碼
咱們看到不論是處理Key爲空仍是不爲空,最後都須要調用addEntry方法,下面來分析一下addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
//擴容,擴容因子是2
resize(2 * table.length);
//計算key的hash值
hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
//根據hash和table的size來計算桶下表,也就是該元素在數組中的位置
bucketIndex = indexFor(hash, table.length);
}
//生成一個新的Entry數組
createEntry(hash, key, value, bucketIndex);
}複製代碼
HashMap擴容
void resize(int newCapacity) {
HashMapEntry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//建立一個新的table數組,容量爲舊數組的2倍
HashMapEntry[] newTable = new HashMapEntry[newCapacity];
//將舊數組中的元素所有轉移到新數組裏面
transfer(newTable);
//將新數組賦值table
table = newTable;
//從新計算閾值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}複製代碼
計算元素的下標
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}複製代碼
生成一個新數組
void createEntry(int hash, K key, V value, int bucketIndex) {
//無論Key是否重複,都會去取下標爲bucketIndex的元素
HashMapEntry<K,V> e = table[bucketIndex];
//而後從新給bucketIndex進行賦值,若是出現了hash衝突,就把最後添加的這個entry的指針指向上一個元素
table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
//size自增
size++;
}複製代碼
基本上到這裏,put方法已經比較清楚了,,而後將hash值跟table的size進行位運算獲得該元素的下標,而後再數組中新增該元素,若是出現了hash衝突,那麼不會刪除該元素,會將最新的entry放在該位置,而且將entry的指針指向上一個元素,下面用一張圖來解釋。
上面這個圖是根據HashMap的原理進行繪製的,我定義了容量爲4(2的整數次冪)的Entry數組,能夠看到每一個Entry都有一個next指針,當有hash衝突的時候,新加入的entry的指針會指向上一個entry,不然指向Null。
public V get(Object key) {
//先判斷Key值是否爲空,爲空單獨處理
if (key == null)
return getForNullKey();
//經過getEntry獲取相應的Value
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}複製代碼
getForNullKey
private V getForNullKey() {
if (size == 0) {
return null;
}
//遍歷整個數組,獲取對應的Value
for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}複製代碼
getEntry
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
//先計算hash值
int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
//經過hash值獲得key在數組中的下標,遍歷此下標entry的鏈表
for (HashMapEntry<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;
}複製代碼
跟put的套路基本同樣,須要注意的是在key非空的時候,須要遍歷entry鏈表上面的全部Key,由於有可能這個鏈表衝突了,就是說不通的key對應的hash值是同樣的,因此須要經過key跟hash雙重判斷
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.getValue());
}複製代碼
removeEntryForKey
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
//獲取當前key對應的下標的entry
HashMapEntry<K,V> prev = table[i];
//將entry給e
HashMapEntry<K,V> e = prev;
//遍歷整個entry下面的整個鏈表
while (e != null) {
HashMapEntry<K,V> next = e.next;
Object k;
if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
//若是是鏈表的head,須要對table從新賦值
table[i] = next;
else
//若是是鏈表的中間位置,只須要改變head的指針便可
prev.next = next;
e.recordRemoval(this);
return e;
}
//將當前的entry傳給pre
prev = e;
//繼續遍歷
e = next;
}
return e;
}複製代碼
public boolean containsKey(Object key) {
//跟移除同樣,調用getEntry
return getEntry(key) != null;
}複製代碼
public boolean containsValue(Object value) {
//Null單獨處理
if (value == null)
return containsNullValue();
HashMapEntry[] tab = table;
//遍歷整個table數組
for (int i = 0; i < tab.length ; i++)
//遍歷entry的整個鏈表
for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value))
return true;
return false;
}複製代碼
private boolean containsNullValue() {
//跟非空同樣
HashMapEntry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
if (e.value == null)
return true;
return false;
}複製代碼
調用Arrays的fill方法
public void clear() {
modCount++;
Arrays.fill(table, null);
size = 0;
}複製代碼
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}複製代碼
public Collection<V> values() {
Collection<V> vs = values;
return (vs != null ? vs : (values = new Values()));
}複製代碼
private Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es = entrySet;
//首次調用爲空,會調用new EntrySet
return es != null ? es : (entrySet = new EntrySet());
}複製代碼
因爲遍歷最後都是調用的都是Collection的iterator方法,看一下他們的實現:
Key持有的Iterator爲KeyIterator
Iterator<K> newKeyIterator() {
return new KeyIterator();
}複製代碼
Value持有的Iterator爲ValueIterator
Iterator<V> newValueIterator() {
return new ValueIterator();
}
private final class ValueIterator extends HashIterator<V> {
public V next() {
return nextEntry().getValue();
}
}複製代碼
Entry持有的Iterator爲EntryIterator
Iterator<Map.Entry<K,V>> newEntryIterator() {
return new EntryIterator();
}
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
public Map.Entry<K,V> next() {
return nextEntry();
}
}複製代碼
上述的三個Iterator都是繼承自HashIterator,只是複寫了next方法而已,因此如今只須要研究一下HashIterator
HashIterator
private abstract class HashIterator<E> implements Iterator<E> {
HashMapEntry<K,V> next; // 下一個須要返回的entry
int expectedModCount; // 當expectedModCount跟modCount不同時,會報異常,快速失敗
int index; // current slot,當前節點
HashMapEntry<K,V> current; // 當前的entry
HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
HashMapEntry[] t = table;
//遍歷整個table,不爲空的時候而後退出循環,此時將next指向第一個元素
while (index < t.length && (next = t[index++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
//獲取下一個元素
final Entry<K,V> nextEntry() {
modCount與expectedModCount不相等的時候,說明有多個線程同時操做Map
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
HashMapEntry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
HashMapEntry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
//刪除某一個元素
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}複製代碼
有幾點須要注意: