public class HashMap1. 一些重要参数 1.1 serialVersionUID属性extends AbstractMap implements Map , Cloneable, Serializable { }
// 序列化版本号 private static final long serialVersionUID = 362498820763181265L;
serialVersionUID适用于java序列化机制。简单来说,JAVA序列化的机制是通过 判断类的serialVersionUID来验证的版本一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。
**具体序列化的过程是这样的:**序列化操作时会把系统当前类的serialVersionUID写入到序列化文件中。当反序列化时系统会自动检测文件中的serialVersionUID,判断它是否与当前类中的serialVersionUID一致。如果一致说明序列化文件的版本与当前类的版本是一样的,可以反序列化成功,否则就失败;
1.2 DEFAULT_INITIAL_CAPACITY属性static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
DEFAULT_INITIAL_CAPACITY是HashMap的默认初始容量,大小为16
1.3 DEFAULT_LOAD_FACTOR属性static final float DEFAULT_LOAD_FACTOR = 0.75f;
DEFAULT_LOAD_FACTOR是HashMap的默认负载因子大小,大小为0.75。
当元素数量 ≥ 容量*负载因子,那么HashMap需要扩容。
1.4 MAXIMUM_CAPACITY属性static final int MAXIMUM_CAPACITY = 1 << 30;
MAXIMUM_CAPACITY属性是HashMap的最大容量。
1.5 TREEIFY_THRESHOLD属性static final int TREEIFY_THRESHOLD = 8;
TREEIFY_THRESHOLD属性是HashMap转变为红黑树实现的阈值。当元素数量大于这个值,就会转变为红黑树。
1.6 UNTREEIFY_THRESHOLD属性UNTREEIFY_THRESHOLD是HashMap由红黑树转变为链表的阈值。当元素数量大于这个值,就会转变为链表。
1.7 MIN_TREEIFY_CAPACITY属性MIN_TREEIFY_CAPACITY是可以将链表转变为红黑树的最小数组容量。如果没有这个限制,假如节点过多,节点数组会频繁地resize。
2. 一些重要属性 2.1 table属性transient Node[] table;
table属性用来存节点。
2.2 entrySet属性transient Set> entrySet;
entrySet属性用来缓存键值对集合。
2.3 size属性transient int size;
size属性用来存map的键值对数目。
2.4 modCount属性transient int modCount;
modCount属性记录了map结构被修改的次数,用于在迭代器中的快速失败。
2.5 threshold属性// (The javadoc description is true upon serialization. // Additionally, if the table array has not been allocated, this // field holds the initial array capacity, or zero signifying // DEFAULT_INITIAL_CAPACITY.) int threshold;
threshold属性记录了下一个需要resize的size大小
2.6 loadFactor属性final float loadFactor;
loadFactor属性记录了当前map的负载因子
2.7 Node内部类static class Nodeimplements Map.Entry { final int hash; final K key; V value; Node next; Node(int hash, K key, V value, Node next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry, ?> e = (Map.Entry, ?>) o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
Node内部类实则表达了一个键值对, 并包含了哈希值和next指针。
2.8 EntrySet内部类final class EntrySet extends AbstractSet> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator > iterator() { return new EntryIterator(); } public final boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry,?> e = (Map.Entry,?>) o; Object key = e.getKey(); Node candidate = getNode(hash(key), key); return candidate != null && candidate.equals(e); } public final boolean remove(Object o) { if (o instanceof Map.Entry) { Map.Entry,?> e = (Map.Entry,?>) o; Object key = e.getKey(); Object value = e.getValue(); return removeNode(hash(key), key, value, true, true) != null; } return false; } public final Spliterator > spliterator() { return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer super Map.Entry > action) { Node [] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (Node e : tab) { for (; e != null; e = e.next) action.accept(e); } if (modCount != mc) throw new ConcurrentModificationException(); } } }
EntrySet内部类用来表示当前HashMap的键集合。
3. 一些工具方法 3.1 hash方法static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这个hash()方法的(h = key.hashCode()) ^ (h >>> 16)过程值得研究。
首先,h >>> 16是将哈希值向右无符号移动16位,也就相当于取了原值的高16位。
0000 0100 1011 0011 1101 1111 1110 0001 >>> 16
得到:
0000 0000 0000 0000 0000 0100 1011 0011
然后,我们再来看一个JDK1.7中的indexFor()方法。在JDK8以后,源码中也大量出现**tab[(n - 1) & hash]**的形式,实际上也是一样的。
static int indexFor(int h, int length) {
return h & (length-1);
}
indexFor()方法的返回值就是table数组中我们想要找到的数组下标。但由于绝大多数情况下length一般都小于2^16即小于65536。所以return h & (length-1)结果始终是h的低16位与(length-1)进行&运算。
所以很显然的是,我们每次在计算下标的时候,都几乎只能用到低位。如果我们想一个办法,把高位也利用起来,那么就可以增加散列的程度。所以hash()方法中选择了与自身的高十六位(h >>> 16)进行异或,来利用到高位。
3.2 tableSizeFor方法
static final int tableSizeFor(int cap) {
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
tableSizeFor方法用于计算参数cap对应的resize阈值。往往出现为下面的语句。
threshold = tableSizeFor(size);3.3 resize方法
final Node[] resize() { // 拿到旧的数组、容量、扩容阈值 Node [] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) {// 如果旧容量不为0 if (oldCap >= MAXIMUM_CAPACITY) {// 如果旧容量已经达到了最大容量 // 令扩容阈值为一个不可能达到的最大值 threshold = Integer.MAX_VALUE; // 直接返回旧数组 return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY //如果旧容量扩大两倍后仍没达到最大容量,设其为新容量 && oldCap >= DEFAULT_INITIAL_CAPACITY)// 并且旧容量大于等于初始容量 // 令新的扩容阈值扩大为两倍 newThr = oldThr << 1; } else if (oldThr > 0) // 如果旧容量为0,且旧扩容阈值大于0 // 那么就让新容量变为旧扩容阈值 newCap = oldThr; else { // 旧容量和旧扩容阈值都为0 // 令新容量为默认初始容量,并计算扩容阈值 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) {// 如果新扩容阈值为0 // 计算当前的容量*负载因子 float ft = (float) newCap * loadFactor; // 设置新扩容阈值为ft或Integer.MAX_VALUE newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE); } // 更新扩容阈值 threshold = newThr; // 创建一个新的table,大小为先前计算出来的新容量 @SuppressWarnings({"rawtypes", "unchecked"}) Node [] newTab = (Node []) new Node[newCap]; // 更新table指针 table = newTab; if (oldTab != null) {// 如果旧数组不为null for (int j = 0; j < oldCap; ++j) {// 遍历之 Node e; if ((e = oldTab[j]) != null) {// 如果j处的旧数组值不为null,存进e // 将旧数组里的值设为null oldTab[j] = null; if (e.next == null)// 如果e.next为null // 在新数组中找到并赋值 newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode)// 如果e这里是一棵树,调用split分割树,并带到新数组中 ((TreeNode ) e).split(this, newTab, j, oldCap); else { // e这里是链表,且e.next不为null // 下面要进行一个链表分割操作,将原链表分为lo串和hi串两串。 Node loHead = null, loTail = null; Node hiHead = null, hiTail = null; Node next; do { // 保存next next = e.next; if ((e.hash & oldCap) == 0) {// 如果位置不用改变 // 将e加入到lo串中 if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else {// 如果位置需要改变 // 将e加入到hi串中 if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null);// 如果e赋为next后不为null // 将lo串放到下标为j的位置处,即不变的位置 if (loTail != null) { loTail.next = null; newTab[j] = loHead; } // 将hi串放到下标为j+oldCap处,因为新位置的hash中高一位是1,那下标就要加一个oldCap if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
先尝试用数学方法确定新容量和新扩容阈值。
随后遍历数组,并深入遍历其中的链表/红黑树。如果是链表,要将他分裂为lo链表和hi链表。
3.4 treeifyBin方法final void treeifyBin(Node[] tab, int hash) { int n, index; Node e; // 如果数组很空或很小,那么就resize() if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) {// hash对应的位置在数组中不为null,就可以进入这个循环 TreeNode hd = null, tl = null; do { // 将节点e转变为一个树节点存到变量p TreeNode p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) // 转变为红黑树 hd.treeify(tab); } }
treeifyBin方法将符合要求的链表转变为一个红黑树。
4. 一些业务方法 4.1 get方法public V get(Object key) {
Node e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node getNode(int hash, Object key) {
Node[] tab;
Node first, e;
int n;
K k;
if ((tab = table) != null // table不为空
&& (n = tab.length) > 0 // table长度不为0
&& (first = tab[(n - 1) & hash]) != null) {// 在算出来的位置拿到的Node不为null,这是链表头
if (first.hash == hash // 检查first的哈希值,假如正确且key也符合
&& ((k = first.key) == key || (key != null && key.equals(k))))
return first;// 直接返回first
if ((e = first.next) != null) {// 如果first链表长度不止为1
if (first instanceof TreeNode) {// 如果table当前位置延伸出的是一个红黑树
return ((TreeNode) first).getTreeNode(hash, key);// 调用getTreeNode方法
}
// 不是红黑树,是链表,遍历查找即可
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
详见上面的注释。用哈希值计算出数组中的位置,再沿着链表或红黑树去找。
4.2 put方法public V put(K key, V value) {
// 调用putVal
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab;
Node p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
// 如果table为null或者为空,先扩容,再把长度赋值为n
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
// 如果找到的指定位置为空,那么在这里用新键值对调用newNode
tab[i] = newNode(hash, key, value, null);
else {
// 如果找到的位置不为空
Node e;
K k;
if (p.hash == hash // 如果哈希值符合
&& ((k = p.key) == key || (key != null && key.equals(k))))// 并且key也吻合
// 将当前节点p赋给e
e = p;
else if (p instanceof TreeNode)// 如果当前p是一个树节点,调用putTreeval方法
e = ((TreeNode) p).putTreeval(this, tab, hash, key, value);
else {// 如果当前是一个链表节点
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {// 令e=p.next,如果它为null
// 令p.next为用新键值对创建的newNode,就是将新节点插在了链表尾
p.next = newNode(hash, key, value, null);
// 如果长度到达了转变为红黑树的最小阈值,那么就转变为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 如果找到了
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
// 令p为e,for循环一开始的时候e=p.next,所以相当于p后移了
p = e;
}
}
if (e != null) { // 如果key已经存在,更新它并返回oldValue
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 检查是否需要扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
详见上面的注释。用哈希值计算出数组中的位置,再在链表或红黑树中搜索,做更新操作或newNode操作。
4.3 remove方法public V remove(Object key) {
Node e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
final Node removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node[] tab;
Node p;
int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {// 检查对应数组位置是否存在链表/树
Node node = null, e;
K k;
V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 如果链表头就是,赋值给node
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
// 如果是树,就调用对应的方法找到,赋值给node
node = ((TreeNode) p).getTreeNode(hash, key);
else {
// 是链表,就遍历查找
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
// 找到了,将当前节点赋值给node
node = e;
// 退出循环,这导致了当找到后,下面的p=e没有执行
break;
}
// 将当前节点赋值给p
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
// 如果是树,用对应方法删除
((TreeNode) node).removeTreeNode(this, tab, movable);
else if (node == p)
// 如果要移除的是链表头,那么根据上面do-while块中的逻辑,node==p,那么我们直接让链表从node.next开始
tab[index] = node.next;
else
// 如果要移除的不是链表头,那么根据上面do-while块中的逻辑node!=p,之前是p->node,删除node
p.next = node.next;
// 处理modCount和size属性
++modCount;
--size;
afterNodeRemoval(node);
// 返回remove掉的节点node
return node;
}
}
return null;
}
找->删,非常清晰的逻辑
4.4 clone方法@Override
public Object clone() {
HashMap result;
try {
result = (HashMap)super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
result.reinitialize();
result.putMapEntries(this, false);
return result;
}
clone()方法对HashMap做了一个浅拷贝。
5. 一些来自JDK8的方法下面的方法来自于重写JDK8中的Map接口中的方法。
一些函数式的方法在此不做解析,如compute()和merge()等。
5.1 getOrDefault / putIfAbsent@Override
public V getOrDefault(Object key, V defaultValue) {
Node e;
return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}
@Override
public V putIfAbsent(K key, V value) {
return putVal(hash(key), key, value, true, true);
}
很好理解,获得实际值或默认值/不存在时插入。
5.2 remove / replace@Override
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
Node e; V v;
if ((e = getNode(hash(key), key)) != null &&
((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
e.value = newValue;
afterNodeAccess(e);
return true;
}
return false;
}
@Override
public V replace(K key, V value) {
Node e;
if ((e = getNode(hash(key), key)) != null) {
V oldValue = e.value;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
return null;
}
当对应键值对存在时,进行删除替换操作。



