美文网首页
HashMap源码之二:get方法

HashMap源码之二:get方法

作者: 涂豪_OP | 来源:发表于2018-08-03 09:21 被阅读49次

        上篇文章分析了HashMap的put方法,既然有put,就肯定有get方法,下面分析get方法:

    /**
         * Returns the value to which the specified key is mapped,
         * or {@code null} if this map contains no mapping for the key.
         *
         * <p>More formally, if this map contains a mapping from a key
         * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
         * key.equals(k))}, then this method returns {@code v}; otherwise
         * it returns {@code null}.  (There can be at most one such mapping.)
         *
         * <p>A return value of {@code null} does not <i>necessarily</i>
         * indicate that the map contains no mapping for the key; it's also
         * possible that the map explicitly maps the key to {@code null}.
         * The {@link #containsKey containsKey} operation may be used to
         * distinguish these two cases.
         *
         * @see #put(Object, Object)
         */
        public V get(Object key) {
            Node<K,V> e;
            //核心就是这个getNode方法,根据key的hash值找
            //Node,找到了就返回Node的value,否则就返回空,没找到
            return (e = getNode(hash(key), key)) == null ? null : e.value;
        }
    

        get方法很简单,没什么好说的,核心是getNode方法:

    /**
         * Implements Map.get and related methods
         *
         * @param hash hash for key     查找的key的hash值
         * @param key the key           查找的key
         * @return the node, or null if none
         */
        final Node<K,V> getNode(int hash, Object key) {
    
            //tab就是HashMap的数组;first是每个通上的节点;
            //n是数组的长度;k是节点的hash值
            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值一样,key也一样,那么直接返回首节点
                if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                //如果首节点不是我们要找的节点,那么遍历吧
                if ((e = first.next) != null) {
                    //如果首节点是红黑树节点,说明桶后面接的是一棵红
                    //黑树,那么就调用getTreeNode方法去红黑树中搜索
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    //如果首节点不是红黑树节点,那么说明桶后面接
                    //的是一个链表,那么遍历这个链表查找,很简单
                    do {
                        //往死里循环,直到此链表遍历完了;查找的依据是hash相同,key也相同
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }
    

        getNode方法也很简单,首先判断首节点是不是我们要找的节点;如果不是的话,就看这个节点是不是红黑树节点,如果是红黑树节点,那么以查找红黑树的方式去搜索;如果是普通节点的话,那么用do ... while循环去遍历这个链表;遍历链表很简单,直接看红黑树的搜索:

    /**
     * Calls find for root node.
     */
     final TreeNode<K,V> getTreeNode(int h, Object k) {
         //首先拿到红黑树的根节点,然后调用find方法去搜索红黑树;root方法
         //太简单,不管;find方法在上一篇文章讲过,但是没分析,这次分析下
         return ((parent != null) ? root() : this).find(h, k, null);
     }
    

        getTreeNode方法很简单,就是简单的调用了find方法,下面分析:

            /**
             * Finds the node starting at root p with the given hash and key.
             * The kc argument caches comparableClassFor(key) upon first use
             * comparing keys.
             * h : 要查找的key的hash值
             * k : key的值
             * kc : key的Class对象,传进来的null
             */
            final TreeNode<K,V> find(int h, Object k, Class<?> kc)
            {
                //find本来就是TreeNode的方法,this代表调用find的TreeNode
                TreeNode<K,V> p = this;
    
                do {
                    //ph是遍历到的节点的hash值;dir是目标key
                    //和红黑树节点的key比较的结果pk是节点的key值
                    int ph, dir; K pk;
    
                    //定义左右子节点和节点q
                    TreeNode<K,V> pl = p.left, pr = p.right, q;
    
                    //如果节点的hash大于目标key的hash,那么在这
                    //个节点的左子树中查找,所以讲p指向p的左子节点
                    if ((ph = p.hash) > h)
                        p = pl;
    
                    //如果节点的hash小于目标key的hash,那么在这
                    //个节点的右子树中查找,所以讲p指向p的右子节点
                    else if (ph < h)
                        p = pr;
    
                    //如果节点的hash和key的hash相等,而且节点的key
                    //和目标key相等,说明找到了,此时返回此节点即可
                    else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                        return p;
    
                    //下面的流程是目标key的hash和节点hash
                    //相等,但是目标key和节点key不等的场景
    
    
                    //pl == null遍历到的节点没有左子节点了,怎么办?当然
                    //是从右子树开始继续搜索,所以这里就将右子节点赋值给p
                    else if (pl == null)
                        p = pr;
    
                    //同上,但是是从左子树开始继续搜索
                    else if (pr == null)
                        p = pl;
    
                    //comparableClassFor(k))的作用是判断目标key是否实现了Comparable
                    //接口,也就是判断目标key是否有可比性,如果返回null说明没实现接口,
                    //没有可比性;可比性dir = compareComparables(kc, k, pk))
                    //!= 0是说明目标key和节点的key具有可比性,dir就存放了对比的结果,
                    else if ((kc != null ||
                              (kc = comparableClassFor(k)) != null) &&
                             (dir = compareComparables(kc, k, pk)) != 0)
    
                        //根据dir赋值左子节点或者右子节点,继续下一轮搜索
                        p = (dir < 0) ? pl : pr;
    
                    //流程走到这,说明目标key和当前节点没有可比性,或者比较不出来
                    //结果,那么从这个节点的右子节点开始继续递归搜索,直到找到为止
                    else if ((q = pr.find(h, k, kc)) != null)
                        return q;
    
                    //如果右子树没有找到,那么从左子树开始继续找
                    else
                        p = pl;
                } while (p != null);
    
                //如果没找到就返回空
                return null;
            }
    

        总的来看,find方法还是蛮简单的,自此,get方法分析完毕。
        下面分析remove方法:

        /**
         * Implements Map.remove and related methods
         *
         * @param hash hash for key key的hash值
         * @param key the key       key本身
         * @param value the value to match if matchValue, else ignored  key对应的value,传进来是空
         * @param matchValue if true only remove if value is equal  是否值删除key和value都匹配的节点
         * @param movable if false do not move other nodes while removing   删除节点后是否移动别的节点
         * @return the node, or null if none
         */
        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;
    
            //一波判断,前面的getNode方法解释过,pass
            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;
    
                //下面的一波操作是跟getNode一毛一样的,不多解释
                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 {
                        do {
                            if (e.hash == hash &&
                                ((k = e.key) == key ||
                                 (key != null && key.equals(k)))) {
                                node = e;
                                break;
                            }
                            p = e;
                        } while ((e = e.next) != null);
                    }
                }
    
                //node就是查找出来的节点。remove是一个重载的方法,有一个参数和两个参数的,两个参数的
                //方法不光要判断key,还要判断value,matchValue就是表示要不要判断value;如果找出来
                //的节点是红黑树节点,那么调用removeTreeNode去红黑树中删除节点,否则删除链表中的节点
                //,链表删除节点很简单,就是把node他爹的next指向node自己的next,不多解释
                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);
                    else if (node == p)
                        tab[index] = node.next;
                    else
                        p.next = node.next;
                    //删除操作也是修改HashMap,所以modCount自增
                    ++modCount;
    
                    //HashMap元素个数自减,没毛病
                    --size;
    
                    //回调,在put方法中解释过,pass
                    afterNodeRemoval(node);
    
                    //返回删除的节点
                    return node;
                }
            }
    
            //没有找到目标节点就是返回空
            return null;
        }
    

        removeNode比较简单,删除的第一步就是查找,跟上面get方法一样,找到节点后就删除,其中链表节点的删除很简单,不分析,下面分析红黑树的删除:

    待定
    
    

    相关文章

      网友评论

          本文标题:HashMap源码之二:get方法

          本文链接:https://www.haomeiwen.com/subject/keusvftx.html