[TOC]
## 初始化HashMap
新建一個HashMap(其實就是創建一個Entry數組)
```
Map<String, Object> map = new HashMap<String, Object>();
```
初始化HashMap(new HashMap())時,會將HashMap的初始容量設置為16,將負載因子設為0.75
```
//默認容量大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//默認負載因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
```
然后繼續初始化HashMap
```
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
//HashMap最大容量是2的30次方 1073741824
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
this.loadFactor = loadFactor;
//初始化HashMap容量的閾值: 16;如果hashMap的容量大于16后,就會hashMap的擴容
threshold = initialCapacity;
init();
}
```
初始化完成后,向HashMap中設置值,即put方法
## 向HashMap中設置值 put(K key, V value)
```
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
/**
* (1) 初始化table,new一個容量大小為16的Entry[]數組
* (2) 設置閾值threshold = 16*0.75(容量*負載因子) = 12
* (3) 如果有必要還會設置散列掩碼值hashing mask value
*/
inflateTable(threshold);
}
// 如果key為空, put一個key為null的值
if (key == null)
return putForNullKey(value);
//計算key的hash值
int hash = hash(key);
//計算Entry數組的下標
int i = indexFor(hash, table.length);
//如果已經有了相同的key,則使用新值替換舊值,并返回舊值
// table[i]有可能是一個鏈表
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++;
//添加key value
addEntry(hash, key, value, i);
return null;
}
```
這里有個比較重要的地方
```
for (Entry<K,V> e = table[i]; e != null; e = e.next)
```
獲取某個下標的HashMap值,可能獲取到多個Entry數據,這個Entry數據是由鏈表組成,所有這里需要用到for循環,就是因為此處,會導致HashMap線程不安全(后面會具體說明原因);
addEntry()源碼,如果Entry數組的實際容量大小大于等于閾值(12),則需要對hashMap進行擴容(即新建一個Entry數組,其大小為現在數組的兩倍大小),并把原Entry數組中的內容移到新Entry數組中,并重新計算key的hash值,作為新Entry數組的下標(參照下面resize()方法);如果Entry數組的實際容量大小小于閾值(12),則在原Entry數組中新添加一組數據。
```
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
//擴容Entry數組
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//新Entry數組的下標
bucketIndex = indexFor(hash, table.length);
}
//向Entry數組中添加一組數據
createEntry(hash, key, value, bucketIndex);
}
```
擴容Entry數組方法:resize()
```
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//新建一個Entry數組
Entry[] newTable = new Entry[newCapacity];
//將原數組的數據移到新數組中
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
```
將原數組移到新數組中,并重新計算每個key的hash值
```
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;
}
}
}
```
向Entry數組中新增一組數據
```
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
//新建一個Entry對象,并賦值,然后添加到table數組中
table[bucketIndex] = new Entry<>(hash, key, value, e);
//添加完數據后,數組大小+1
size++;
}
```
向HashMap中添加key、value的流程基本就完成了。
## 從HashMap中獲取值 get(Object key)
```
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
```
獲取key對應的Entry對象
```
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;
}
```
管中窺豹,其他方法不展開了。
## 為什么HashMap不是線程安全的
(1)put操作線程不安全
在多線程的情況下,線程A和線程B同時對一個map的數組位置(table[1])進行put操作,兩個線程同時得到table[1]鏈表的頭結點,線程A執行完put操作后,將新值存儲到了頭結點的位置,而此時線程B才執行完put操作,則線程B存儲的值會覆蓋掉線程A存儲的值,這樣會導致線程A存儲的值丟失。
(2)刪除鍵值時線程不安全:原理同上
(3)當數組容量超過閾值時,同resize數組,從新建一個比原來容量大一倍的數組,此時當多個線程同時操作一個map的resize時,最后一個線程的resize會覆蓋掉前面所有線程的resize操作,導致其他線程的數據都會丟失。