Map是一个存储key-value键值对的集合,其中Map中的key不允许重复 可以为null,value可以是重复的,key与value一一对应,单看key 是不可重复且无序的集合,就是一个Set集合;单看value是一个可重复且无序的集合,就是一个Collection集合。如图(底层是Entry[])
在HashMap类中定义了初始化容量为DEFAULT_INITIAL_CAPACITY = 1 << 4;的常量
扩容的条件:
- 当容量达到当前容量与默认加载因子的乘积 并且待插入的位置不为空时 扩容为原来的2倍
- 当待插入的位置上链表长度超过8 并且数组的容量为超过64时 扩容为原来的2倍
树化条件:
- 当待插入的位置上链表长度超过8,并且数组的容量超过64 转化为红黑树
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//默认初始化容量16
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量,
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子
static final int TREEIFY_THRESHOLD = 8;//树化临界值
static final int UNTREEIFY_THRESHOLD = 6;//反树化临界值
static final int MIN_TREEIFY_CAPACITY = 64;//最小树化容量
首先,在调用无参构造方法时(并没有初始化数组transient Node<K,V>[] table;的长度),执行putVal方法,这里面的hash方法的作用是 为传进来的key值通过hash()方法得出来一个哈希值(因为key是不允许重复的,所以在这里要先得出一个key的哈希值以便后面判断key值是否重复)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//得到一个尽可能不同的哈希值
}
//put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
然后进入putVal()方法,首先声明一个Node类型的tab和p,其中Node类型为Map.Entry的实现类
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)//把table赋值给tab,第一次添加时由于table没被初始化,所以结果为true进入if
n = (tab = resize()).length;//执行resize()方法初始化底层数组Entry类型的数组,此方法会返回一个Node数组,把这个数组的长度赋值给n 跳到下一块代码(resize()方法),此时n=16
if ((p = tab[i = (n - 1) & hash]) == null)//(n - 1) & hash长度-1“与运算”刚才得到的哈希值,其结果一定是[0,n-1],同时把结果赋值给索引 i 然后把tab[i]赋值给p,并判断位置p上是否为空,如果为空,直接添加成功
tab[i] = newNode(hash, key, value, null);//添加成功
else {//这是p位置上有数据的情况
Node<K,V> e; K k;//声明临时变量e(Node类型) 和 k(就是key的类型)
if (p.hash == hash &&//判断 p 位置上的数据的key的哈希值是否于要加入的数据的key的哈希值是否相等 ,如果不相等 整体返回false,直接跳到else
((k = p.key) == key || (key != null && key.equals(k))))//如果相同 继续判断key的equals是否相等 如果相等 就会把原来的数据覆盖
e = p;//覆盖原来的数据
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//跳到这里
for (int binCount = 0; ; ++binCount) {//死循环,取出在此位置(p)上的链表上的所有数据,逐个比较
if ((e = p.next) == null) {//如果不存在下一个节点
p.next = newNode(hash, key, value, null);//添加成功
if (binCount >= TREEIFY_THRESHOLD - 1) // 这里是当链表超过8时转换为红黑树的判断
treeifyBin(tab, hash);//转红黑树的方法
break;
}
if (e.hash == hash &&//逐个判断链表上的数据的key与待加入的数据的key的哈希值比较,如果不相等直接添加成功,并把链表的最后一个数据指向这个数据,如果相同继续判断其equals是否相等,如果相等覆盖掉原来的数据,否则也是添加成功,并把链表的最后一个数据指向这个数据
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
//内部类Node
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//0
int oldCap = (oldTab == null) ? 0 : oldTab.length;//第一次调用时 返回的结果是0
int oldThr = threshold;//也是0
int newCap, newThr = 0;//也是0,所以直接跳到下面else (直接找我标记的②)
if (oldCap > 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; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // ② 接上面的调到else处
newCap = DEFAULT_INITIAL_CAPACITY;//此时底层的数组才被真正的初始化其长度为16;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//这个变量是 map 扩容的临界值 ,当其长度达到这个值的时候就要考虑扩容的问题,但不是立刻就会进行扩容
}
if (newThr == 0) {//此时newThr 为16 if语句进不去 直接执行下面
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;//把newThr赋值给threshold =16
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//相当于Node<K,V>[] newTab = (Node<K,V>[])new Node[16];
table = newTab;//16
if (oldTab != null) {//第一次oldTab为null,直接跳到最后返回结果newTab
for (int j = 0; j < oldCap; ++j) {//第二次及以后 遍历数组的每一个位置,放进新数组table
Node<K,V> e;//把每一个位置上的元素赋值给e
if ((e = oldTab[j]) != null) {//判断e是否为空
oldTab[j] = null;//先把原来的数组 j (j一直变化)位置上的元素置空
if (e.next == null)//如果e的下个元素为空,如果为空说明只有一个元素
newTab[e.hash & (newCap - 1)] = e;//e.hash & (newCap - 1)计算给e分配的位置,和jdk7中的indexOf方法作用是一样的。
else if (e instanceof TreeNode)//判断e是否为红黑树的节点
((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;
if ((e.hash & oldCap) == 0) {
if (loTail == null)//扩容前的尾结点为空,说明链表遍历完了
loHead = e;//把e放在最后
else
loTail.next = e;//把e放在最后的下一个
loTail = e;//遍历下一个
}
else {
if (hiTail == null)//新的链表尾结点为空,说明链表遍历完了
hiHead = e;//把e放在最后
else
hiTail.next = e;//把e放在最后的下一个
hiTail = e;//遍历下一个
}
} while ((e = next) != null);//没有下一个节点时 结束循环
if (loTail != null) {//如果扩容前尾结点不为空
loTail.next = null;
newTab[j] = loHead;//扩容前的头结点 放在 j(上面的 j ) 索引的位置上
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}