红黑树在HashMap中的应用

由于红黑树出色且稳定的性能,Java的很多集合框架都引入了红黑树结构。在JDK1.8中,我们常用的HashMap也对红黑树进行了引入,本文就通过源码来分析红黑树在HashMap中的应用。
摘要由CSDN通过智能技术生成

在上一篇文章:红黑树(Red-Black Tree)解析 中我们了解了二叉查找树以及红黑树的概念和特性,并且对查找、插入和删除操作的实现源码进行了详细的剖析。其复杂的操作流程保证了红黑树的五条特性始终能够被满足,从而使得红黑树操作的时间复杂度为O(logN)。也正因为如此,Java的很多集合框架都引入了红黑树结构以提高性能。在JDK1.8中,我们常用的HashMap也成功傍身红黑树策马奔腾,下面就让我们一起看看HashMap是如何入手红黑树的。

这里我们依然从查找,插入和删除三个常用操作来进行分析。除去这三个操作之外还有一个地方与红黑树结构密切相关–resize扩容操作,关于HashMap的扩容我们在另一篇文章中有详述,这里就不再重复,有兴趣的童鞋请戳这里(Java中集合的扩容策略及实现)

  • 相关成员变量

    首先,先介绍一下相关的成员变量

        //哈希表中的数组,JDK 1.8之前存放各个链表的表头。1.8中由于引入了红黑树,则也有可能存的是树的根
        transient Node<K,V>[] table;
    
        //树化阈值。JDK 1.8后HashMap对冲突处理做了优化,引入了红黑树。
        //当桶中元素个数大于TREEIFY_THRESHOLD时,就需要用红黑树代替链表,以提高操作效率。此值必须大于2,并建议大于8
        static final int TREEIFY_THRESHOLD = 8;
    
        //非树化阈值。在进行扩容操作时,桶中的元素可能会减少,这很好理解,因为在JDK1.7中,
        //每一个元素的位置需要通过key.hash和新的数组长度取模来重新计算,而1.8中则会直接将其分为两部分。
        //并且在1.8中,对于已经是树形的桶,会做一个split操作(具体实现下面会说),在此过程中,
        //若剩下的树元素个数少于UNTREEIFY_THRESHOLD,则需要将其非树化,重新变回链表结构。
        //此值应小于TREEIFY_THRESHOLD,且规定最大值为6
        static final int UNTREEIFY_THRESHOLD = 6;
    
        //最小树化容量。当一个桶中的元素数量大于树化阈值并请求treeifyBin操作时,
        //table的容量不得小于4 * TREEIFY_THRESHOLD,否则的话在扩容过程中易发生冲突
        static final int MIN_TREEIFY_CAPACITY = 64;
    
  • 查找

    HashMap中的查找是最常用到的API之一,调用方法为map.get(key),我们就从这个get方法看起:

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    可以看到这个方法调用了两个内部方法:hash和getNode,下面依次来看这两个方法:

    //hash方法对传入的key进行了哈希值优化,具体做法为将key的哈希值h无符号右移16位之后与h本身按位异或,
    //相当于将h的高16位于低16位按位异或。这样做的原因在于一个对象的哈希值即使分布再松散,其低几位发生冲突的概率也较高,
    //而HashMap在计算index时正是用该方法的返回值与(length-1)按位与,结果就是哈希值的高位全归零,只保留低几位。
    //这样一来,此处的散列值优化就显得尤为重要,它混合了原始哈希值的高位与低位,以此来加大低位的松散性。
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    /**
     * Implements Map.get and related methods
     *
     * @param hash hash for key  //此处传入的就是上面hash方法的返回值,是经过优化的哈希值
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //上文提到的计算index的方法:(n - 1) & hash,first是这个数组table中index下标处存放的对象
        if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                //如果first对象匹配成功,则直接返回
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                //否则就要在index指向的链表或红黑树(如果有的话)中进行查找
                if (first instanceof TreeNode)
                    //如果first节点是TreeNode对象,则说明存在的是红黑树结构,这是我们今天要关注的重点
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key); 
                //否则的话就是一个普通的链表,则从头节点开始遍历查找
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

    重点来了,这里关注下TreeNode.getTreeNode(hash, key)方法,这是1.8中引入红黑树后新增的操作,它对于HashMap在哈希冲突多发,产生长链表的情况下的查找效率有着极大的提升:

    /**
    * Calls find for root node.
    */
    //定位到树的根节点,并调用其find方法
    final TreeNode<K,V> getTreeNode(int h, Object k) {
        return ((parent != null) ? root() : this).find(h, k, null);
    }
    
    final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
        TreeNode<K,V> p = this; //p赋值为根节点,并从根节点开始遍历
        do {
            int ph, dir; K pk;
            TreeNode<K,V> pl = p.left, pr = p.right, q;
            if ((ph = p.hash) > h) //查找的hash值h比当前节点p的hash值ph小
                p = pl; //在p的左子树中继续查找
            else if (ph < h)
                p = pr; //反之在p的右子树中继续查找
            else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                return p; //若两节点hash值相等,且节点的key也相等,则匹配成功,返回p
    
        /****---- 下面的情况是节点p的hash值和h相等,但key不匹配,需继续在p的子树中寻找 ----****/
    
            else if
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值