HashMap是什么
hashMap是java最常使用的一种kv键值对,允许null作为key,无序,并且不允许有重复的key,hashMap是非线程安全的。
说明:本文分享的代码基于JDK1.7
HashMap的继承关系
实现原理
HashMap 是一个散列表,由数组+链表组成的,每个数组元素存储的是一个链表的头结点。数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,该类实现了Map接口,具有很快的访问速度
存储结构
HashMap的Entry<k,V>
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
......
}
创建HashMap时,默认创建数组大小16的Entry数组
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);
// 计算table初始大小,默认16
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
//根据初始大小,加载因子(默认为0.75)计算扩容的大小,()
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
HashMap的put方法
put方法源码
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
// 根据key计算hash, 在根据hash计算出index
int hash = hash(key);
int i = indexFor(hash, table.length);
// 遍历table[index]的链表判断key是否存在
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,添加新元素
modCount++;
addEntry(hash, key, value, i);
return null;
}
在put的源码中可以看到,判断key是否为空,如果是空,调用putForNullKey(value)方法添加,如果key不为空,根据key的hash计算table的index,然后判断遍历table判断key是否已经存在,存在替换key中的值,不存在调用addEntry(hash, key, value, i);添加Entry到table中。
putForNullKey方法源码
private V putForNullKey(V value) {
// 对应key=null的情况,HashMap默认保存index=0的链表中
// 判断index=0的链表中是否存在key为null
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;
}
}
// 不存在添加新元素,这里默认添加到table[0]中
modCount++;
addEntry(0, null, value, 0);
return null;
}
key为null默认保存到table[0]中,首先遍历table[0]中的链表判断key是否已经存在,如果存在则更新key中的值,不存在调用addEntry(hash, key, value, i);添加Entry到table中
addEntry()源码
void addEntry(int hash, K key, V value, int bucketIndex) {
// 判断是否需要进行扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
// 扩容为旧容量的2倍
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
// 扩容之后添加新元素
createEntry(hash, key, value, bucketIndex);
}
判断size是否超过了设置的扩容值,如果是,调用resize()方法进行扩容,扩容的大小为原先容量的大小的2倍,扩容完创建|Entry并保存
resize()方法源码
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
// 判断容量是否超出最大值
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
// 按照新的大小创建新的table
Entry[] newTable = new Entry[newCapacity];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
// 将数据移到新的table中
transfer(newTable, rehash);
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
计算容量,按新的容量创建新的table,然后调用transfer()方法进行迁移
transfer()方法源码
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
// 遍历table中的每个元素,重新计算index,将元素添加到新的table中
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;
}
}
}
循环旧table中的所有元素,重新计算hash和index,保存到新的table中, 扩容结束
扩容结束我们回到addEntry中,addEntry中扩容结算后调用createEntry()方法
createEntry()源码
void createEntry(int hash, K key, V value, int bucketIndex) {
// 取出table[index]链表中的第一个元素
Entry<K,V> e = table[bucketIndex];
// 创建Entry,将新创建的Entry放入到table[index]中。
// 创建Entry时,将之前table[index]的entry做为新创建Entry的next节点进行保存
// 所以新元素默认保存到链表的头部
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
创建Entry,把他保存到table[index]链表的头结点。
HashMap的get方法
get()方法源码
public V get(Object key) {
//判断key是否为空,是,从table[0]中的链表中查找
if (key == null)
return getForNullKey();
// 不为空,遍历table中所以元素查找
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
判断key是否为空,是,调用getForNullKey()方法中查找,否调用getEntry()方法中查找
getForNullKey()源码
private V getForNullKey() {
// 遍历table[0]链表中的所有元素查找
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
getEntry()方法源码
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key);
// 遍历table中的元素查询
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
// 判断hash是是否相等,判断key是否equals
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
注意:在HashMap中,我们知道随着HashMap中元素的数量越来越多,发生碰撞的概率将越来越大,所产生的子链长度就会越来越长,这样势必会影响HashMap的存取速度。为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理,该临界点就是HashMap中元素的数量在数值上等于threshold(table数组长度*加载因子)。但是,不得不说,扩容是一个非常耗时的过程,因为它需要重新计算这些元素在新table数组中的位置并进行复制处理。所以,如果我们能够提前预知HashMap 中元素的个数,那么在构造HashMap时预设元素的个数能够有效的提高HashMap的性能。