[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操作,导致其他线程的数据都会丢失。