HashMap底層儲存原理
阿新 • • 發佈:2019-02-01
HashMap在日常工作中使用場景非常多,程式設計師都知道是HashMap是執行緒非安全的,但是底層是以什麼方式儲存的?本人仔細研讀了一下原始碼,也只是掌握了核心的儲存功能,並沒有把全部程式碼看明白,但是對於理解hashMap的儲存結構完全夠了。
儲存結構
- hashmap底層是以陣列方式進行儲存。將key-value對作為陣列中的一個元素進行儲存。
- key-value都是Map.Entry中的屬性。其中將key的值進行hash之後進行儲存,即每一個key都是計算hash值,然後再儲存。每一個Hash值對應一個數組下標,陣列下標是根據hash值和陣列長度計算得來。
- 由於不能的key有可能hash值相同,即該位置的陣列中的元素出現兩個,對於這種情況,hashmap採用連結串列形式進行儲存。
- 下圖描述了hashmap的儲存結構圖
Entry結構分析
- Entry是hashMap中封裝key-value鍵值對的,主要包括如下屬性
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;// map中key值,可以為null。
V value; // map中的value值,可以為null。
Entry<K,V> next;// 連結串列引用,防止key值不同,hash值相同。
int hash; // 每個key的hash值
// 建構函式
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
// 同一個key時,新值替換舊值,返回舊值
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
// key值重寫equals方法
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
// 重寫hashCode值
public final int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
public final String toString() {
return getKey() + "=" + getValue();
}
// 其他方法省略
}
HashMap屬性分析
- HashMap的屬性分析
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
/**
*預設情況下,hashmap大小為16.即1<<4就是1乘以2的4次冪=16
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* hashMap的最大值
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 預設載入載入因子,即使用空間達到總空間的0.75時,需要擴容。
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 宣告hashmap一個空陣列。
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
/**
* 最開始時,hashmap是一個空陣列。
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
/**
* map的元素的個數
*/
transient int size;
/*
* hashmap的實際儲存空間大小。這個空間是總空間*載入因子得出的大小。
* 比如預設是16,載入因子是0.74。則threshold就是12。
*/
int threshold;
/**
* 載入因子,即使用空間達到總空間的0.75時,需要擴容。
*/
final float loadFactor;
/**
*
*/
transient int modCount;
/**
* threshold這個值的最大值就是Integer.MAX_VALUE
*/
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
put方法
- put(key,value)方法是hashmap中最重要的方法,使用hashmap最主要的就是使用put,get兩個方法。put方法底層儲存又是什麼呢,我們可以從put方法的原始碼進行分析
public V put(K key, V value) {
// 首次儲存元素,初始化儲存空間
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// 如果key為null,則將null放入元素的第一個位置
if (key == null)
return putForNullKey(value);
// 計算key的hash值
int hash = hash(key);
// 根據key的hash值,陣列長度計算該Entry<key,value>的陣列下標
int i = indexFor(hash, table.length);
/**
**如果當前key的已經存在於map中,則將新值替換成舊值。
**/
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 判斷同一個key,既要判斷hash值相同,還要判斷key是同一個key,因為
// 相同的key有可能hash值也相同。雙重判斷保證是同一個key。
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++
modCount++;
// 將新增key-value鍵值對新增中map中。
addEntry(hash, key, value, i);
return null;
}
addEntry方法
- addEntry方法是將新增的key-value鍵值對存入到map中。該方法主要完成兩個功能:
1.1. 新增新元素前, 判斷是否需要對map的陣列進行擴容,如果需要擴容,則擴容空間大小是多少?
1.2. 對於新增key-value鍵值對,如果key的hash值相同,則構造單向列表。 - 從原始碼分析結果如下:
/**
** hash:key的hash值
** key:儲存的鍵
** value:儲存的value物件值
*** bucketIndex:陣列下標位置,即key-value在陣列中的位置。
**/
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);
}
// 往陣列中新增新的key-value鍵值對
createEntry(hash, key, value, bucketIndex);
}
createEntry
- 該方法主要完成兩個功能,第一是新增新的key到Entry陣列中,第二就是對於不同key的hash值相同的情況下,在同一個陣列下標處,構建單向連結串列進行儲存。
void createEntry(int hash, K key, V value, int bucketIndex) {
// 取出當前位置的元素,如果是新新增的key,則e為null,已經有的元素為不為空。
Entry<K,V> e = table[bucketIndex];
// 新增新的key-value值或構建連結串列
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}