HashMap原理分析
阿新 • • 發佈:2018-12-26
HashMap基於雜湊表的Map介面實現,其中每個元素是一個key-value對,允許使用null值和null鍵。內部使用單鏈表的方式解決hash衝突的問題,當容量不足時,它會自動增長容量。
HashMap中的部分欄位:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//預設初始容量為16 static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量為2的30次方 static final float DEFAULT_LOAD_FACTOR = 0.75f;//預設載入因子為0.75 transient Entry[] table;//入口陣列,每個Entry儲存一條連結串列的頭結點 transient int size;//入口陣列中已用槽的數量 transient int modCount;//被修改的次數 int threshold;//容量閥值,用於判斷是否需要調整HashMap的容量(threshold = 容量 * 載入因子) final float loadFactor;//載入因子實際大小
HashMap的幾種建構函式:
/*指定初始容量initialCapacity和載入因子loadFactor的建構函式*/ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY;//最大初始容量不允許超過2的30次方 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); //初始化欄位 }
/*指定初始容量initialCapacity的建構函式*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/*預設建構函式*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
/*傳入一個Map作為引數的建構函式*/ public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
Entry入口陣列:
Entry數組裡的每個元素儲存一個key-value對,當前key的hash值、以及指向下一個key-value對的指標next。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
final int hash;
Entry<K,V> next;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
HashMap中的get操作:
public V get(Object key) {
if (key == null) //如果key == null,進入table[0]所指示的連結串列處查詢
return getForNullKey();
int hash = hash(key.hashCode()); //獲取key的hash值
for (Entry<K,V> e = table[indexFor(hash, table.length)]; // 遍歷指定連結串列找出value值
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
//找不到值返回null
return null;
}
/*key為null的元素儲存在入口陣列的第一個位置(即table[0])處所指示的連結串列中*/
private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
/*將hash值與入口陣列的長度進行某種形式上的取模運算,返回指定下標*/
static int indexFor(int h, int length) {
return h & (length-1);
}
get方法的步驟:程式碼化為:
if key == null
Entry<K, V> head = table[0];
for Each node in list
if found
return value;
end for
return null;
if key != null
int hash = key.hashCode();
int i = hash % table.length;
Entry<K, V> head= table[i];
for Each node in list
if found
return value;
end for
return null;
HashMap中的put操作:
public V put(K key, V value) {
if (key == null) //若key為null,則將key-value對加入到table[0]處
return putForNullKey(value);
int hash = hash(key.hashCode()); //獲取key的hash值
int i = indexFor(hash, table.length); //將hash值與入口陣列的長度進行取模運算返回指定位置下標
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))) { //若key值對應的key-value對存在,則用新value值替換掉舊value值
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 若key值對應的key-value對不存在,則將此key-value對儲存至入口陣列中
modCount++;
// 將key-value對新增到table[i]處
addEntry(hash, key, value, i);
return null;
}
/*putForNullKey()的作用是將key為null的key-value對新增到table[0]位置*/
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;
}
}
// 如果沒有存在key為null的鍵值對,則直接新增到table[0]處
modCount++;
addEntry(0, null, value, 0);
return null;
}
/*addEntry()的作用是新增一個Entry元素。將key-value對插入指定位置,bucketIndex是位置索引*/
void addEntry(int hash, K key, V value, int bucketIndex) {
// 頭插法插入新Entry元素
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
// 若HashMap的實際大小大於或等於閥值,則重新調整HashMap的大小
if (size++ >= threshold)
resize(2 * table.length);
}
/*resize()的作用是重新調整HashMap的大小,newCapacity是調整後的單位*/
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
// 新建一個HashMap,將"舊HashMap"的全部元素新增到"新HashMap"中,
// 然後,將"新HashMap"賦值給"舊HashMap"。
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
/*將HashMap中的全部元素都轉移到newTable中*/
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
put方法的步驟: