概述
我们可以看的JDK源码有很多,最被人熟知的当然就包括HashMap了,我当初学的时候什么都不懂,就只有印象那就是很麻烦,麻烦到在网上随便搜罗点文章,总结,就算是我会了。所以我就认认真真花费了1天的时间对我们最最熟悉不过的HashMap做了一点点整理,希望能帮助到我彻底解决掉梦魇。
HashMap 详解
属性
- 默认初始化容量 16
- 默认的加载因子 0.75f
- entrySet Entry的节点
- 加载因子
- 容量的最大值 1<<30
- 链表树化的数组长度:长度64
- modCount 由于HashMap是线程不安全的类,所以在操作HashMap中的数据时,会记录这个修改的次数,当使用迭代器遍历HashMap中的数据时,先把这个值赋给迭代器的expectedModCount,迭代的过程中比较这两个值,如果不相等直接抛异常,也就是源码注释中写的fail-fast机制
- 序列化ID
- table 存储 Node节点的数组
- threshold 扩容的阈值 阈值又是容量和加载因子的乘积 没有赋值的默认值
- TREEIFY_THRESHOLD 默认是8 链表长度>8 会生成红黑树
- UNTREEIFY_THRESHOLD 默认是6 链表长度<6 会变回红黑树
13.14 这里只有entrySet 没有keySet 和 values 是AbstractMap的属性
static class Node<K,V> implements Map.Entry<K,V>
1 hash
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
-->
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
类比String 和 Object 再看HashMap的 hashcode
String的hashCode
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
Object
实现方式略有不同,其实在idea实现hashcode时,也提供给了几种方式去实现
public native int hashCode();
2 clear
遍历删除Node节点内容
//transient Node<K,V>[] table;
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
3 capacity
capacity 是 default的类型 且是 HashMap 不能调用到的,原因是default
4 containsKey
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
// key和 hash(key)
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//判断数组不为空 长度不为0 找到数组位置
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
/*
判断第一个节点hash等于传进来的hash(key) &&
第一节点的key=传进来的key ||
first是数组某个位置上的Node节点
*/
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);
/*如果不是树 ,遍历链表每个元素 有则返回 从第二个才开始遍历
e是first.next的节点 第二节点
判断 hash值相同 key相同或者key.equels相同
*/
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
5 get方法
同理getNode 返回节点后 取出 节点的value值
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
*6 put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
public V putIfAbsent(K key, V value) {
return putVal(hash(key), key, value, true, true);
}
//putMapEntries 方法 readObject 方法都用到了putVal
// 还有一个putAll方法 调用了putMapEntries
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
//上边是put的所有的方法
-->
// hash(key) key value
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 拿到Node数组 如果数组为空 那么扩容
if ((tab = table) == null || (n = tab.length) == 0)
//扩容看resize() n为新扩容的大小
n = (tab = resize()).length;
// 在新扩容(可能不需要扩容)的实际位置 如果数组位置为空 直接赋值
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {//如果已经存在值
//对e做赋值的过程
Node<K,V> e; K k;
// e为Node hash相同 key也相同 就赋值 这里针对的是存在 数组位置上的
//Node就和你插入的key相同 就赋值给e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//树的处理
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// p是什么 某个位置上的Node(已经存在) 而且是存在值的(可能next为空)
else {
//开始循环 循环的是链表
for (int binCount = 0; ; ++binCount) {
//如果是链表末尾,也就是没有next链表
if ((e = p.next) == null) {
//在末尾创建新节点并且赋值
p.next = newNode(hash, key, value, null);
/*如果链表长度>=7 那么就创建treeifyBin 在treeifyBin中
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
在判断数组长度<64 时 只会扩容
*/
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//在循环过程中找到了相同的key 和 hash
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//循环过程中 直接覆盖掉p p永远是e的next节点
p = e;
}
}
// 对e 赋值完成 判断是否是覆盖操作 对接到上一个break;之前 如果是覆盖 //的话
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//判断accessOrder才会执行
afterNodeAccess(e);
//覆盖操作 返回老的值
return oldValue;
}
}
//设置修改频次
++modCount;
//如果达到扩容的阈值 那么会扩容 上面的老值就肯定不需要扩容了
if (++size > threshold)
resize();
//判断evict才会执行
afterNodeInsertion(evict);
return null;
}
eg:
Integer put = map.put(4, 1);
System.out.println(put);
Integer put1 = map.put(4, 2);
System.out.println(put1);
null
1
*7 resize方法
default方法,同样是HashMap才能调用到
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//int threshold; 扩容的阈值
int oldThr = threshold;
// 初始化新容量 和 新阈值
int newCap, newThr = 0;
// 如果目前的数组长度>0
if (oldCap > 0) {
//如果数组长度大于 最大容量 那么就返回原先的数组
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 如果新数组扩容1倍 仍然小于最大容量 &&
// 老数组长度大于默认长度(一般不会有问题)
// 修改新阈值
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 如果老阈值>0 也就是在oldCap<=0 时 会将新阈值赋值(设置过阈值)
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
// 如果阈值是默认的话 使用这条链路 新容量和 新阈值都是在resize中创建的
// 剩余用到默认容量DEFAULT_LOAD_FACTOR 都是构造器中的赋值
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//设置新的阈值
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 设置新的Node大小 为新的容器newCap 大小
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//切换了newTab 给到了 Table
table = newTab;
// 下面全是是判断以内的 老数组有值
if (oldTab != null) {
//遍历老节点数组
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
// 如果老节点数组的某个位置 不为空
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
//某个数组位置就1个节点
if (e.next == null)
/*newTab是新的数组 还未存放 在扩容情况下,如果老数组只有1个节 点,那么新的数组 &(数组.length - 1) 就一定还是一个*/
newTab[e.hash & (newCap - 1)] = e;
//树节点的处理办法
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 剩余的解决办法 条件前提:有链表,不是树,目前正在遍历数组的Node
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;
/*判断扩容后的16->32 最高为 是否为1 ,如果是1 那么是新
数组的位置,比如之前是数组5的位置(默认长度是16)
0101 - 00101 10101
5 5 21
加上扩容的大小就是新的链表通过hash(key)可能存放的位置
*/
if ((e.hash & oldCap) == 0) {
//尾节点为空 那么就证明从来没存放过 就将loHead赋值
if (loTail == null)
loHead = e;
/* 如果尾节点有值,那么此时有头节点,尾节点也被赋值,
尾节点的next就赋值此值 添加链表内容(也证明了链表是无
序的)
*/
else
loTail.next = e;
/*尾节点必赋值,在第一次时,尾节点和头节点是相同的,
第二次以后尾节点就不断的变化 是链表的末尾
*/
loTail = e;
/* 之后的循环loHead 就不进去了,先赋值循环出来的节点 给尾节点,重新赋值尾节点 loTail 此时头节点loHead是可 以遍历到这个单链表的 ioTail不行 */
}
//对称和上述 赋值内容不同
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
//结论就是 链表内的值 16 - 32
16-1= 1111
32-1= 11111
/** 在4位&的情况下 和 5位& 只会重新分配给2个数组位置(原数组 位置和新的数组位置)
*/
//else的结尾 循环结束
// 放入了2个位置 放入的是head(完整的链表)
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
*8 remove iterator
private class Itr implements Iterator<E>
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
-->
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//删除的前提 有值 并且 对应的数组位置也有值
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//如果数组上的Node节点是要删除的节点,那么就赋值node=p
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//如果不是数组上的节点是删除对象,那么查找链表
else if ((e = p.next) != null) {
//是否为树
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
//非树的话,遍历链表 判断key和hash 赋值node
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
//p是链表的上一个e
p = e;
} while ((e = e.next) != null);
}
}
//节点Node已经赋值 已经找到需要删除的Node
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//树的删除
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
// 如果是node==p 那么是数组位置的Node 需要重新指向next的Node保证
else if (node == p)
tab[index] = node.next;
//如果是通过链表找到的Node 这个节点的下一个节点指向 上一个节点p的
//下一个节点
else
p.next = node.next;
//修改操作频次 减少值的大小 返回节点
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
ConcurrentHashMap详解
put 方法
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
//取hash值
int hash = spread(key.hashCode());
int binCount = 0;
//取Node数组到tab
// Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; 修改sizeCtl
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
//没有设置才需要初始化大小
tab = initTable();
//获取Node节点
// 传入tabAt Node数组 和 写入的数组位置 cas(Unsafe)的方式获取对应的Node
//如果数组位置没有值进入
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
// 比较并且交换hash算出来的某个数组位置的 Node
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//ForwardingNode 特殊需求的Node
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//锁的对象是确定的某个Node节点
/*根据上述判断条件,确定这个位置
1 取hash 2如有必要设置初始化大小 3 Node数组位置为空直接复制
4 MOVED 5 然后对具体的Node加锁(也像极了分段锁)
5 做了覆盖 创建 等操作
*/
synchronized (f) {
//取出来某个位置的Node 和上次取出来判空的含义相同 像双端检索机制
if (tabAt(tab, i) == f) {
//判断Node节点在数组上的那个Node 的hash值
if (fh >= 0) {
binCount = 1;
//遍历节点
for (Node<K,V> e = f;; ++binCount) {
K ek;
//hash值相同 覆盖value 退出
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
//如果不相同 那么 取下一个节点,直到链表的最后一个
//节点 然后结束
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// fh <0 并且是 树
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
} // 退出同步代码块
//遍历算出来的是 链表的长度 是否生成链表 ,如果是覆盖 会返回旧值
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//对于参数的设置 返回旧值就没有了
addCount(1L, binCount);
return null;
}
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
//循环 条件是等待初始化 table没有内容
while ((tab = table) == null || tab.length == 0) {
//sc为-1 让出线程 线程会让出CPU执行权,让自己或者其它的线程运行
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// 将sc 置为-1 然后开始初始化,
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
//确定初始化大小 设置了sizeCtl就用这个
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
ConcurrentHashMap概念
table:默认为null,初始化发生在第一次插入操作,默认大小为16的数组,用来存储Node节点数据,扩容时大小总是2的幂次方。
nextTable:默认为null,扩容时新生成的数组,其大小为原数组的两倍。
sizeCtl :默认为0,用来控制table的初始化和扩容操作,具体应用在后续会体现出来。
-1 代表table正在初始化
-N 表示有N-1个线程正在进行扩容操作
其余情况:
1、如果table未初始化,表示table需要初始化的大小。
2、如果table初始化完成,表示table的容量,默认是table大小的0.75倍,居然用这个公式算0.75(n - (n >>> 2))。
Node:保存key,value及key的hash值的数据结构。
ForwardingNode MOVED
final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
ForwardingNode(Node<K,V>[] tab) {
super(MOVED, null, null, null);
this.nextTable = tab;
}
}
只有table发生扩容的时候,ForwardingNode才会发挥作用,作为一个占位符放在table中表示当前节点为null或则已经被移动