static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 默认容器大小 16
static final int MAXIMUM_CAPACITY = 1 << 30; 容器最大容量 2的30次方
static final float DEFAULT_LOAD_FACTOR = 0.75f; 加载因子
static final int TREEIFY_THRESHOLD = 8; 判断HashMap是使用链表还是树(长度大于8则使用树)
static final int UNTREEIFY_THRESHOLD = 6; 在HashMap中如果发现长度小于6则会由树转化为链表
static final int MIN_TREEIFY_CAPACITY = 64; 只有当容器中的键值对数量大于64才会使用树,(Otherwise the table is resized if too many nodes in a bin.)
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//容器节点的hash值
final K key;//容器节点的key
V value;//容器节点的值
Node<K,V> next;//容器节点的下一个节点 --> HashMap的链表形式
Node(int hash, K key, V value, Node<K,V> 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);
}
//HashMap 在put新的值的时候会返回之前的值
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;
}
}
这里举个例子说明HashMap在插入新的值的时候会返回之前的值
//根据key生成hash值 如果key为空则返回null 如果不为null将key生成的hash值h与h右移16位做亦或运算(将hash值的高位和低位做亦或的目的防止生成的hash值重复)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/*计算容器的大小
将传来的cap分别右移1 2 4 8 16 位与cap-1的值做或运算(逻辑运算符效率高)
cap-1的目的因为HashMap的长度是2的指数大小,这样在cap等于2的指数会造成容器过大
例如: 0000 0000 0000 1000
>>>1 0000 0000 0000 0100
0000 0000 0000 1100
>>>2 0000 0000 0000 0110
0000 0000 0000 1110
>>>4 0000 0000 0000 0111
0000 0000 0000 1111
... ....
此时n = 15 所以return了16 但我们只要8就够了,所以这里cap要减一
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
//初始化容器
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) //初始化长度不能小于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);
//设置加载因子
this.loadFactor = loadFactor;
//获取容器大小
this.threshold = tableSizeFor(initialCapacity);
}
//设置带初始化大小的HashMap
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//不带初始化大小的HashMap
public HashMap() {
//默认加载因子 0.75
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//拷贝Map容器
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
//HashMap拷贝
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();//要拷贝的map的大小
if (s > 0) {//这里只有要拷贝的map大小不为0
if (table == null) { //新建未初始化的容器接收
//计算不大于阈值的容器长度 加1的原因是如果计算出来的结果是小数那么会向下取整,但实际上容器大小要向上取整。
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);//判断需要的容器大小是否大于最大容量
if (t > threshold)//所需要的容量大于容器现有容量时,重新获取容器大小
threshold = tableSizeFor(t);
}
else if (s > threshold)//容器已经初始化过,所需要的容量大于容器现有容量时,重新获取容器大小
//预先扩大容量
resize();
//put元素
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
//重新设置容器大小
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {//如果之前的容器大小超出最大容量则不再扩容
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//容器扩容成原来大小的两倍 条件是扩容前的容器大小大于等于默认初始化大小 16
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // **double threshold**
}
//如果初始化容器时指定了threshold长度 那么就用指定的长度
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//如果初始化容器时没有指定threshold长度 那么就用默认的长度 16
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//这里其实只是说默认实际使用长度在阈值范围内而已
}
if (newThr == 0) {// 计算指定了initialCapacity情况下的新的 threshold
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
//创建一个新的容器把老的容器的值拷贝进来
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {//大条件是老的容器有值
for (int j = 0; j < oldCap; ++j) {//循环
Node<K,V> e;//变量节点
if ((e = oldTab[j]) != null) {//指当前数组索引有值
oldTab[j] = null;
if (e.next == null)//如果数组索引只有一个值,那么通过hash与运算cap-1来计算新容器的数组索引将值放进去
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)//如果当前索引是树
//红黑树插入数据
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
//既然单元素和树都有了,那么这边肯定是链表的插入了,这么长的代码不要急慢慢看^_^
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;//因为while的循环终止条件是e.next==null所以e肯定是有子节点的,这里是将指针后移一位
if ((e.hash & oldCap) == 0) {//这边是将一个链表拆成两个链表放到扩容后的容器里
//链表的插入
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
//链表的插入
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
//如果loTail!=null说明链表有值,将链表的头指针存入数组j的索引里
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
//如果hiTail !=null说明链表有值,将链表的头指针存入数组j+oldCap的索引里(因为扩容是扩大一倍的)
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
/*
上面的代码(e.hash & oldCap) == 0还是很巧妙的,首先oldCap肯定是2的指数大小
那么写成二进制就一定是0001 0000 0000 0000 这种形式 而e.hash在对应oldCap
为1的位置要么为1要么为0 且与运算只有当都为1才为1,这样这个判断实际每次都是
随机为0或者oldCap本身,这样就很巧妙的将一个链表拆成两个链表
*/
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
//定义两个树用来存放old的树 开始还是链表后面判断是否转换成树
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
//循环的终止条件是下一个节点为null
for (TreeNode<K,V> e = b, next; e != null; e = next) {
next = (TreeNode<K,V>)e.next;
e.next = null;
if ((e.hash & bit) == 0) {//这个上面谢过了哈
if ((e.prev = loTail) == null)//初始没有元素并且e没有前节点放入头节点
loHead = e;
else//否则放在尾节点
loTail.next = e;
loTail = e;//指针下移
++lc;
}
else {
if ((e.prev = hiTail) == null)//初始没有元素并且e没有前节点放入头节点
hiHead = e;
else//否则放在尾节点
hiTail.next = e;
hiTail = e;//指针后移
++hc;
}
}
//链表不为空
if (loHead != null) {
//长度小于等于 UNTREEIFY_THRESHOLD 不转化成树
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);//将树类型的链表变为普通链表
else {//长度大于 6 转化成红黑树
tab[index] = loHead;
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
//链表不为空
if (hiHead != null) {
//长度小于等于 UNTREEIFY_THRESHOLD 不转化成树
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);//将树类型的链表变为普通链表
else {//长度大于 6 转化成红黑树
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
final Node<K,V> untreeify(HashMap<K,V> map) {
Node<K,V> hd = null, tl = null;
//将树的类型的节点转换为Node<>(p.hash, p.key, p.value, next);类型节点,重新生成链表
for (Node<K,V> q = this; q != null; q = q.next) {
Node<K,V> p = map.replacementNode(q, null);
if (tl == null)
hd = p;
else
tl.next = p;
tl = p;
}
return hd;
}
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
return new Node<>(p.hash, p.key, p.value, next);
}
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;//定义红黑树的根节点
//循环链表
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;//指针后移
x.left = x.right = null;//确保当前节点左右子节点为null
if (root == null) {//如果根节点为空,当前节点作为根节点
x.parent = null;
x.red = false;
root = x;
}
else {//根节点不为空,当前节点作为子节点
//初始化变量
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = root;;) {//从根节点开始遍历,没有边界是为了当(p = (dir <= 0) ? p.left : p.right) == null 不满足时,也就是说要插入的位置有值,那么从要插入的位置起重新开始
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)//当前节点的hash值>要插入的元素的hash值
dir = -1;//标记
else if (ph < h)//当前节点的hash值<要插入的元素的hash值
dir = 1;//标记
//这个写过了哈,hash冲突时用比较器
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);//标记
TreeNode<K,V> xp = p;
/*
当dir标记小于等于 0 -->p=p.left 此时插左节点
当dir标记大于 0 -->p=p.right 此时插右节点
且p要为null
*/
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//红黑树节点的插入
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
root = balanceInsertion(root, x);//红黑树的平衡,这个我写过一篇博客,这里就不细讲了
break;
}
}
}
}
moveRootToFront(tab, root);
}
/*
hashMap的红黑树不仅是一个树,还是一个双向链表,这里因为之前做了平衡,所以红黑树的根节点可能早就不是最初的根节点了,所以需要确保现在的根节点在链表的头位置
*/
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;
//根节点不为null 并且数组不为null 数组的长度大于0
if (root != null && tab != null && (n = tab.length) > 0) {
int index = (n - 1) & root.hash;//计算这个树在数组中的索引
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];//获取数组此索引位置的元素即在平衡前的根节点,也是链表的头节点
if (root != first) {//这说明红黑树的平衡改变了根节点,下面其实就是把现在的根节点放到链表的头节点作为数组的索引元素
Node<K,V> rn;//定义root的next节点
tab[index] = root;//把root节点放数组索引位
TreeNode<K,V> rp = root.prev;//定义root的prev节点
if ((rn = root.next) != null)//root节点后有节点
((TreeNode<K,V>)rn).prev = rp;//root的next节点的前节点指向root的前节点(原本是指向root,现在指向root的前节点,相当于把root拿出,因为root现在在头指针的位置)
if (rp != null)//root的prex(前节点)不为空
rp.next = rn;//上面是next的prev指向root的prev,那么现在当然是root的prev的next指向root的next了
if (first != null)//头指针不为null的时候,将现在的头指针的prev指向root
first.prev = root;
root.next = first;//root的next指向first
root.prev = null;//root是头节点所以前节点为null
}
//这一步是校验是否满足红黑树和链表的结构
assert checkInvariants(root);
}
}
上面moveRootToFront方法直接写注释还是有点抽象,这里画了图,如下图:
//根据key获取 value
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
//定义变量
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
/*查看数据需要满足一下条件
1)数组不为空
2)数组长度>0
3)通过hash计算出该元素在数组中存放位置的索引,而且该索引处数据不为空null
*/
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//从索引处查找第一个元素 (链表或者红黑树的头元素节点)
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果没找到寻找下一个
if ((e = first.next) != null) {
//判断是否是红黑树的类型
if (first instanceof TreeNode)
//遍历红黑树查找
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {//不是红黑树,那么是链表,遍历链表查找
//判断链表的hash值和key值和查找元素是否相同
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
//红黑树的遍历节点
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null)?root() : this)//找到头节点
.find(h, k, null);//寻找到元素
}
//找到头节点
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)//父节点为空返回头节点
return r;
r = p;//指针前移(指向当前节点的头节点)
}
}
//寻找红黑树中的元素
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this;//指针指向当前节点
do {
int ph, dir; K pk;//定义变量
TreeNode<K,V> pl = p.left, pr = p.right, q;//定义pl pr分别为红黑树的左 右子节点
if ((ph = p.hash) > h) //如果当前节点的hash值>要寻找的元素的hash值
p = pl;//指针指向当前节点的左子节点
else if (ph < h)//如果当前节点的hash值<要寻找的元素的hash值
p = pr;//指针指向当前节点的右子节点
else if ((pk = p.key) == k || (k != null && k.equals(pk)))//当前节点是需要寻找的节点
return p;//返回当前节点
else if (pl == null)//当前节点的左子节点为null
p = pr;//指针指向当前节点的右子节点
else if (pr == null)//当前节点的右子节点为null
p = pl;//指针指向当前节点的左子节点
/*
comparableClassFor(k)) != null 判断key不为空
compareComparables(kc, k, pk))比较是否相等
kc --> 传入key的数据类型
pk --> 当前节点的数据类型
所以下面这段代码的意思就是当hash值冲突时,那么判断传入的key是否相同(通过比较器)
*/
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;//如果dir<0那么说明要找的元素在当前节点的左边,反之在右边
else if ((q = pr.find(h, k, kc)) != null)//当hash冲突时,并且通过比较器也无法判断,那么从右子节点开始递归,
return q;//返回当前节点
else
p = pl;//指针指向当前节点的左子节点
} while (p != null);//当前节点不为null作为循环条件
return null;//跳出循环说明没找到
}
这里补充一下 else if ((q = pr.find(h, k, kc)) != null){return q;} else p = pl;在看源码时一直很疑惑为什么只递归右节点而不递归左节点,后来想了下,因为先递归右节点,所以就算后面有了递归左节点很明显是执行不到的,所以后面直接令p=pl;就行了。