一、数据结构
1、HashMap由内部类Entry的数组组成,该类是一个单向链表。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
}
二、基本属性
1、默认大小为16
static final int DEFAULT_INITIAL_CAPACITY = 16;
2、默认因子为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
3、阀值 ,大小为HashMap大小*因子。
int threshold;
1、默认构造方法。采用默认大小和默认因子,阀值为12.
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
2、put方法。
public V put(K key, V value) {
//判断key是否为空,如果为空,增将它插入到头部
if (key == null)
return putForNullKey(value);
//将key取hash值,再调用HashMap自己的hash方法。
int hash = hash(key.hashCode());
//获取当前的key散列完后所在数组的位置
int i = indexFor(hash, table.length);
//如果当前的HashMap中存在该key,增替换该key的value,并且将原来的value返回。
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;
}
private V putForNullKey(V value) {
//null作为key一定要插入到数组的第0个元素上。
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;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
//获取新增key所在的数组头节点。
Entry<K,V> e = table[bucketIndex];
//将新增节点插入的头节点之前。
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
//如果此时数组大小超过阀值就需要resize。
if (size++ >= threshold)
resize(2 * table.length);
}
3、get方法
public V get(Object key) {
//如果key为null的话,只会在第0个数组上去找
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
//定位到数组具体位置,循环遍历单向链表。
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.equals(k)))
return e.value;
}
return null;
}
4、resize方法
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);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
void transfer(Entry[] newTable) {
//获取旧数组
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
//对旧数组节点释放
src[j] = null;
do {
//临时存储当前节点的下一节点
Entry<K,V> next = e.next;
//获取当前节点在新数组中的位置
int i = indexFor(e.hash, newCapacity);
//将新数组中当前位置的节点指向当前节点
e.next = newTable[i];
//使当前节点作为新数组该位置的头部
newTable[i] = e;
//对下一个节点进行遍历
e = next;
} while (e != null);
}
}
}
5、removeEtryForKey方法
final Entry<K,V> removeEntryForKey(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
//定位当前key所在的数组位置
int i = indexFor(hash, table.length);
//存储前一节点
Entry<K,V> prev = table[i];
//存储当前节点 Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
//这种情况发生的条件是:key正好是头节点的情况下,也就是第一次遍历,直接将当前节点的下一节点指向数组的当前位置。
if (prev == e)
table[i] = next;
//非第一次遍历,之前存储的前一个节点直接指向当前节点的下一个节点
else
prev.next = next;
e.recordRemoval(this);
return e;
}
//将当前节点保存为前节点
prev = e;
//获取下一个节点,继续遍历
e = next;
}
return e;
}
四、总结
1、HashMap是一个由单向链表的构造的类,认真阅读并且思考源码后,对于单向链表的的理解,有非常大的提高。
2、通过对源码的阅读,知道了HashMap在什么情况下进行resize。可以在初始化时进行最优考虑,尽量避免resize的发生。
3、HashMap和HashTable的主要区别有两点:HashMap可以以null作为key进行插入,而HashTable不行。HashTable中的方法都加入了synchronized关键字。