文章目录
Java8—HashMap
put方法
public V put(K key, V value) {
// 先对key做hash值的计算
return putVal(hash(key), key, value, false, true);
}
1, hash
static final int hash(Object key) {
int h;
//这里就没有1.7的那么复杂了, 做了一些右移和异或, 因为有了红黑树加入
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2, putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {//evict在hashmap中没有用, linkedlist里使用
//1.7是entry, 现在是node
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果数组为空调用resize: 包括了初始化, 扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//计算出数组下标, p就是当前下标的元素
if ((p = tab[i = (n - 1) & hash]) == null)
//如果这个数组为空, 就new一个node放到这个下标下面
tab[i] = newNode(hash, key, value, null);
else {
//这个下标数组元素不是空的
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//如果key想等,就替换掉
e = p;
else if (p instanceof TreeNode) //如果key不相等, 判断是否是一个树类型的节点
//如果已经是红黑树了,就调用putTreeVal方法, 把值插入到红黑树中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//key不相等,而且不是数类型的,那就是链表
//遍历链表
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) { //如果p.next等于空, 说明遍历完了,就插入
//尾插法
p.next = newNode(hash, key, value, null);
//判断元素大小是否>8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//树化的时候已经有9个元素了
//3,树化
treeifyBin(tab, hash);
break;
}
//如果key相等调出覆盖
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//找到一样的就更新value
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();//4 扩容
afterNodeInsertion(evict);//在hashmap中没有用, linkedlist里使用
return null;
}
3,树化treeifyBin
//链表长度大于=8
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//如果tab是空的 或者 数组长达<64调用 resize ,不会去数化, 长度大于64才进行树化
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
//扩容, 初始化
resize();
//判断当前数组下标元素是否为空, 为空再执行下面
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
//遍历链表, 把链表里的值放到TreeNode对象里, 并生成双向链表 (转为红黑树的第一步)
TreeNode<K,V> p = replacementTreeNode(e, null);
//replacementTreeNode()= return new TreeNode<>(p.hash, p.key, p.value, next);
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);
}
}
3.1TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;//双向链表 // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
3.2树化方法-treeify
/**
* Forms tree of the nodes linked from this node.
*/
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;
if (root == null) {
x.parent = null;
x.red = false;
root = x;//把第一个节点设置为root节点, 黑色
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;//相当于k的class是什么
//这里从root节点开始查找 x代表插入的节点, p代表红黑树的root节点
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
//根据hash值判断放在节点的那边, ph是root节点hash, h 是插入节点hash
if ((ph = p.hash) > h)//root > h 就往左
dir = -1;//往左
else if (ph < h)
dir = 1;//往右
//comparableClassFor(k)这里就是判断key是否实现了comparable接口, 方便后面比较key
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
//上面为false走下面, 也就是说key实现了comparable接口再走下面判断
(dir = compareComparables(kc, k, pk)) == 0)
//上面就是比较key的值, ==0 说明key值相同
dir = tieBreakOrder(k, pk);//会返回1和-1
/*tieBreakOrder比较了4个值 :
tieBreakOrder(): if (a == null || b == null ||
(d = a.getClass().getName().compareTo(b.getClass().getName())) == 0)
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
identityHashCode使用了native修饰//作用, 获取hashcode, 用户自定义了hashcode也不影响,用时用来防止用户自定义了hashcode方法的情况
*/
TreeNode<K,V> xp = p;
//dir<=0 , p就为root节点的左子节点,就是移动根节点, 遍历红黑树
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//遍历到p没有子节点, 就停止,赋值
x.parent = xp;//传入的节点的父节点设置为, 上面临时的节点xp
if (dir <= 0)//判断插入左边还是右边
xp.left = x;
else
xp.right = x;
//红黑树插入调整方法
root = balanceInsertion(root, x);
break;
}
}
}
}
//移动根节点, 设置双向链表
moveRootToFront(tab, root);
}
//1,把root节点移动到数组上面来, 相当于把红黑树根节点放到原来链表放的数组上面
//转换完成后,红黑树的节点里的next, prev 还是没有变, 也是一个双向链表
//2,把root节点替换成双向链表的头节点
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;
if (root != null && tab != null && (n = tab.length) > 0) {
int index = (n - 1) & root.hash;//这里拿到的下标和原来的root下标一致的
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
if (root != first) {//如果root节点不是链表的头节点, 就把root的这个链表放到链表头节点
Node<K,V> rn;
tab[index] = root;
TreeNode<K,V> rp = root.prev;
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
//判断这个红黑树的各项条件是否相符
assert checkInvariants(root);//assert可选的,需要添加启动vm参数 -ea
}
}
设置成双向链表有什么好处
上面这个方法用到了prev 这个属性
是双向链表, 就方便了把root节点移动到链表的头节点
assert
正常情况
public class test {
public static void main(String[] args) {
assert 1==2;
System.out.println("1"); //输出1
System.out.println("2"); //输出2
}
}
vm参数添加-ea
在执行
报错
4,扩容resize
1.7中还会去判断下当前数组节点是不是!=null
//pu方法中的
if (++size > threshold)
resize();//4 扩容,初始化
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//老数组大于0
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 { // 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<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;
//1只有一个元素的情况,直接计算新下标放进去
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)//2是一个红黑树
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order//3链表
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
//转移链表的区别
//1.7中链表转移是计算一个转移一个, 新数组下标两种情况, 1, =oldindex 2, = oldindex+old.length
//1.8中是先计算出放在1情况的组成一个链表, 2情况组成一个链表 再把这两个情况的链表移动到新数组下面去
do {
next = e.next;
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);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
1.7,和1.8扩容的区别
转移链表的区别
- 1.7中链表转移是计算一个转移一个, 新数组下标两种情况, 1, =oldindex 2, = oldindex+old.length
- 1.8是根据
if(e.hash & oldCap == 0)
情况1 =0 情况 2 != 0 - 1.8中是先计算出放在1情况的组成一个链表, 2情况组成一个链表 再把这两个情况的链表移动到新数组下面去
4.1, 扩容方法红黑树的情况split
- 如果这个红黑树能像链表那也拆分成两个比较小的链表就拆分成链表
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
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
//遍历红黑树的双向链表
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)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;//计算链表大小
}
else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;//计算链表大小
}
}
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)//如果低位的链表个数小于6
tab[index] = loHead.untreeify(map);//改成链表,把treenode改成node,重新赋值
else {//低位链表个数>6
tab[index] = loHead;// 直接移动到新数组上
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);//高位链表不为空重新树化
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
get方法
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;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//first是数组的第一个元素
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))//判断数组第一个元素key是否相等
return first;//相等直接返回了
if ((e = first.next) != null) {//判断是否有next节点
if (first instanceof TreeNode)//判断是不是红黑树
return ((TreeNode<K,V>)first).getTreeNode(hash, key);//是直接查询树节点
do {//链表情况了, 遍历链表 key相等就返回
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}