HashMap的创建
public HashMap() {
table = (HashMapEntry<K, V>[]) EMPTY_TABLE;
threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
}
public HashMap(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("Capacity: " + capacity);
}
if (capacity == 0) {
@SuppressWarnings("unchecked")
HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE;
table = tab;
threshold = -1; // Forces first put() to replace EMPTY_TABLE
return;
}
if (capacity < MINIMUM_CAPACITY) {
capacity = MINIMUM_CAPACITY;
} else if (capacity > MAXIMUM_CAPACITY) {
capacity = MAXIMUM_CAPACITY;
} else {
capacity = Collections.roundUpToPowerOfTwo(capacity);
}
makeTable(capacity);
}
public HashMap(int capacity, float loadFactor) {
this(capacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException("Load factor: " + loadFactor);
}
/*
* Note that this implementation ignores loadFactor; it always uses
* a load factor of 3/4. This simplifies the code and generally
* improves performance.
*/
}
构造函数中涉及到了capacity、loadFactor两个参数,这两个参数是影响HashMap性能的重要参数。
capacity代表了创建哈希表的初始容量,loadFactor代表哈希表容量自动增加之前可以达到多满,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75
==事实上虽然第三个构造函数中可以指定loadFactor但是实际HashMap实现中并没有使用参数中的值而只是使用了默认的0.75==
如图:HashMap底层实现为数组table,数组的每一项又是一个链表HashMapEntry,而构造函数中capacity指的就是数组的长度
/**
* Allocate a table of the given capacity and set the threshold accordingly.
* @param newCapacity must be a power of two
*/
private HashMapEntry<K, V>[] makeTable(int newCapacity) {
@SuppressWarnings("unchecked") HashMapEntry<K, V>[] newTable
= (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity];
table = newTable;
threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity
return newTable;
}
static class HashMapEntry<K, V> implements Entry<K, V> {
final K key;
V value;
final int hash;
HashMapEntry<K, V> next;
HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
this.key = key;
this.value = value;
this.hash = hash;
this.next = next;
}
...
}
HashMap的数据存入
@Override public V put(K key, V value) {
//key为空时 调用putValueForNullKey方法单独处理,这也是为什么HashMap支持null数据存取的原因
if (key == null) {
return putValueForNullKey(value);
}
//计算key的hash值
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
//!!!计算hash对应的数组位置(h&(length - 1)就相当于对length取模,而且速度比直接取模快得多,这是HashMap在速度上的一个优化。)
int index = hash & (tab.length - 1);
//tab是否存在该hash值,如果存在则在数组对应的链表中查找是否有对应的key,如果有则新值覆盖旧值
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
//存在hash值和key值均相同的数据,执行新值覆盖旧值的动作
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;//新值覆盖旧值
return oldValue;//返回旧值
}
}
//上面没有找到对应的key值 创建一个
// No entry for (non-null) key is present; create one
modCount++;
//判断是否需要扩容
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
//数组添加新的节点
addNewEntry(key, value, hash, index);
return null;
}
HashMap的读取
public V get(Object key) {
//key为null时,调用entryForNullKey得到对应的value
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
return e == null ? null : e.value;
}
//计算key对应的hash值
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
//查找tab是否存在对应的key,如果存在返回对应的value
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
return e.value;//返回查找的value
}
}
return null;
}
HashMap的底层数组长度总是2的n次方,在构造函数中存在:capacity <<= 1;这样做总是能够保证HashMap的底层数组长度为2的n次方。当length为2的n次方时,h&(length - 1)就相当于对length取模,而且速度比直接取模快得多,这是HashMap在速度上的一个优化
HashMap中需要做到尽量的分布均匀且充分利用空间,这块的算法后面单说。