在HashMap的API中定义中有具体说明“Note that this implementation is not synchronized.”,此类是不同步方法,HashMap数据结构在单线程应用中可正常使用。在blog中看到有人在并发环境中使用HashMap时,出现过占用CPU100%问题,结合HashMap源码我们对出现占用CPU问题进行分析。
一、创建HashMap
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
threshold=initialCapacity:设置容量,默认(DEFAULT_INITIAL_CAPACITY=16)
loadFactor:设置负载因子,默认(DEFAULT_LOAD_FACTOR=0.75)
二、存值
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;
}
首先判断table属性是否进行初始化容量大小,使用inflateTable方法进行初始化容量:
private void inflateTable(int toSize) {
//计算toSize大于等于最接近number的2的冪数
int capacity = roundUpToPowerOf2(toSize);
//用计算出的容量*负载因子,获得下次调整的容量大小
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//初始化table属性
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
threshold 为第一步设置的容量的值,设置完容量后继续向下执行:
//hashMap允许存储null值的原因
if (key == null)
return putForNullKey(value);
//获取Key的hash值
int hash = hash(key);
//根据Key的hash值和table属性的容量计算出key该放在table中的索引坐标
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;
}
}
获取到key在table中的索引,取出table中该索引对应的key和value信息,如果key和当前key相同,将新value替换oldvalue并将oldValue返回。
//记录hashMap修改的次数
modCount++;
将key 和 value键值对添加到table属性中
addEntry(hash, key, value, i);
三、储存键值对
void addEntry(int hash, K key, V value, int bucketIndex) {
//当前table的长度大于或等于根据负载因子计算的下一次扩充的值时
if ((size >= threshold) && (null != table[bucketIndex])) {
//table 长度重新扩充,并重新获取key的新的hash值及其新的索引
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
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++;
}
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
......
}
在看到createEntry方法的源码时,我们就能将HashMap中的数据模型构建出来,即数组与链表相结合使用,当存在多个hash值一样时,存在数组table的同一个索引上,放在该索引上存储的链表的第一个位置上。
结构如下所示,通过向链表顶部添加来解决hash冲突问题。
table[0]=Entry<?,?>
table[1]=Entry<?,>=>next=>Entry<?,?>
table[2]=Entry<?,>=>next=>Entry<?,?>=>next=>Entry<?,?>
接下来我们看下resize方法,resize方法主要作用是对table属性进行扩容
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);
}
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;
}
}
}
当HashMap的长度满时