【HashMap源码三】Java8-HashMap源码学习笔记(JDK1.8--put方法-get方法)

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

image-20220322215417637

//链表长度大于=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

image-20220322225158030

在执行

报错

image-20220322225240721

4,扩容resize

1.7中还会去判断下当前数组节点是不是!=null

image-20220322230035796

    //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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值