private static int roundUpToPowerOf2(int number) { return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; }
Integer.highestOneBit方法是獲取不大於自己的2^n的值,那該方法具體含義是:html
獲取新的數組容量值,若是給定值大於等於最大的容量,則返回最大容量,不然:若是容量小於等於1,則返回1,不然返回大於等於給定值的最接近的2^n的值java
容量爲何要是2^n次方呢?且看以下代碼:數組
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); }
給方法是查找h(ash)在數組的索引位置;那如今看length爲何要是2^n次方呢?點此查看app
保證&以後的數據不大於lengththis
length-1以後,最低的n位都是1,那與h的&運算以後的值便是h的最低n位spa
採用length-1而不是直接length是由於2^n最低一位是0,那&運算以後數據都分佈在偶數位,不是隨機的.net
private void inflateTable(int toSize) { // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; initHashSeedAsNeeded(capacity); }
擴容:先獲取最接近而且>=指定容量的的數值做爲擴容後的容量;擴容界限就是取容量*加載因子和最大容量+1的最小值;從新初始化table;擴容以後的table沒有任何數據,因此在相關調用操做以後會有數據從新分配操做,比較耗時,因此在初始化一個hashmap的時候最好指定容量避免擴容操做發生code
public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); }
get方法:htm
若是key==null,則單獨獲取blog
private V getForNullKey() { if (size == 0) { return null; } for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; }
若是size==0,則直接返回null;
遍歷table,判斷key,返回value
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; }
根據key獲取Entry:
獲取hash值
獲取數組索引
獲取數組中索引的第一個entry
遍歷entry,若是hash值相等而且(key相等或者equal),則返回當前entry便可
public boolean containsKey(Object key) { return getEntry(key) != null; }
判斷key是否存在只是調用getEntry方法判斷是否爲null便可
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; } } modCount++; addEntry(hash, key, value, i); return null; }
put方法:
若是table爲空數組,則先擴容到擴容界限(threshold)的數組,若是是默認的初始化方法,則threshold=容量;執行該方法以後。threshold=容量*加載因子;
若是key==null,單獨存入該值,putForNullKey
根據hash和length獲取table索引,找出第一個entry
遍歷entry,若是找到則從新設置新值,返回舊值,不然新添加一個entry(addEntry)
private V putForNullKey(V value) { for (Entry<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); return null; }
存入key==null的value,直接查找table的第一個位置(index=0),若是找到則從新設置新值,返回舊值,不然添加新的entry;
key==null的值所有在table【0】的entry上
void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); }
建立新的entry:
先檢查是否知足擴容條件:size>=threshold&&null!=table[bucketIndex];
若是知足擴容,計算新的hash和數組索引(bucketIndex)
建立新的entry(createEntry)
注意事項:size是針對hash表裏的全部數據的容量,而擴容是指數組擴容
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); }
先獲取oldTable的length,若是已經是最大長度,則無需擴容,而且將threshold設爲Integer.MAX_VALUE,不可擴容!
建立新的Entry數組,將舊的table數據添加到newTable中(transfer)
設置新的table和threshold
void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } }
簡單理解爲:數據拷貝
獲取新的數組長度,遍歷table數組;
遍歷每一個鏈表:重定向到新的數組和鏈表中
如今遍歷到[1,0]即table[1]下的第一個entry,計算得出在新的i=5,則該entry的下一個是newTable[5],newTable[5]=entry
從以上能夠看出,新進入newTable的數據在後進入的next下
void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
建立新的Entry:
先根據索引(bucketIndex)獲取數組中的元素(e)
建立新的entry,位於鏈表第一個entry,而e是當前新的entry的next
public void putAll(Map<? extends K, ? extends V> m) { int numKeysToBeAdded = m.size(); if (numKeysToBeAdded == 0) return; if (table == EMPTY_TABLE) { inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold)); } /* * Expand the map if the map if the number of mappings to be added * is greater than or equal to threshold. This is conservative; the * obvious condition is (m.size() + size) >= threshold, but this * condition could result in a map with twice the appropriate capacity, * if the keys to be added overlap with the keys already in this map. * By using the conservative calculation, we subject ourself * to at most one extra resize. */ if (numKeysToBeAdded > threshold) { int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); if (targetCapacity > MAXIMUM_CAPACITY) targetCapacity = MAXIMUM_CAPACITY; int newCapacity = table.length; while (newCapacity < targetCapacity) newCapacity <<= 1; if (newCapacity > table.length) resize(newCapacity); } for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); }
將一個集合添加到現有集合中:
先獲取要添加結合的元素數量
若是現有集合是空集合,擴容:當前threshold和根據添加元素容量計算的新容量的最大值
假如添加的集合元素數量>threshold,則判斷當前table是否須要擴容
此處是保守估計新的集合添加以後的容量:可是能夠保證最多隻有一次調用resize方法!
若是numKeysToBeAdded <=threshold,即便在put方法致使擴容也至多有一次:擴容至兩倍,那threshold也會變爲兩倍
若是numKeysToBeAdded >threshold,若是:targetCapacity>table.length,則在put方法可能會致使resize,不然newCapacity一定大於table.length,在此處resize,put方法就不會resize了