public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
首先HashMap 继承自AbstractMap(抽象类) 实现了Map接口。
在new HashMap<K, V>()时,通过底层代码可以知道:
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
其中loadFactor为float型,指hash表的负载因子;DEFAULT_INITIAL_CAPACITY 为常量,是hash表的初始容量,初值为16;threshold为int型,是hash表的装载阈值,反应了hash表的装载程度。接下来就会初始化一个Entry类型的数组,初始容量为16;而其中table的类型Entry是Hashmap的一个静态内部类。
static class Entry<K,V> implements Map.Entry<K,V>,并且该类没有无参构造函数,
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
当使用hashMap.put(K,V)时,
public V put(K key, V value) {
if (key == null) //判断键是否为null
return putForNullKey(value); //添加键为null的值
int hash = hash(key.hashCode()); //计算二次计算key的hash值,避免冲突
int i = indexFor(hash, table.length); //根据hash值确定元素存放在表中的索引
for (Entry<K,V> e = table[i]; e != null; e = e.next) { //定位到table中的位置后,取出当前的链表
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //判断key是否重复
V oldValue = e.value; //重复的时候返回重复key的value值
e.value = value;
e.recordAccess(this); //这句的作用是什么不知道
return oldValue;
}
}
modCount++; // modCount是不可被序列化的,具有可见性的,
//用来记录hashmap的修改次数
addEntry(hash, key, value, i); //添加一个键值对
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
//当hash表的大小>=阈值时进行扩容,hash表的大小变为原来的两倍。
resize(2 * table.length);
}
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
//直接让原来table[bucketIndex]位置上的表头为新元素的next值,说明,每次插入元素都会在表位置中的表头。
next = n;
key = k;
hash = h;
}
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {//当hash表的容量到达最大值时不再扩容
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable); //将原来hash表中的内容复制到新的hash表中
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
Hashmap中只允许有一个空键的原因:
从代码中可以看出,当第二次添加空键的时候,会将原来null所对应的值覆盖,而且,空键所对应的key存放在表的第一个位置table[0].通过addEntry(0, null, value, 0),在第一次添加空键元素的时候,可以保证null所对应的元素在table[0]链表的表头。
private V putForNullKey(V value) {
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;
}