HashMap源码解析(下)

35 阅读4分钟

目录

 

前言

get方法源码解析

remove方法源码解析

repalce方法源码解析


前言

        上一期详细介绍了HashMap的底层 的数据结构,源码中的几个重要的属性,构造方法,核心方法put 方法和resize扩容方法。详情请查看 HashMap源码解析(上) 。本期主要结合源码介绍一下剩余的几个常用方法:get方法,remove方法,replace方法。

get方法源码解析

返回哈希表中key对应的value,如果此映射不包含键的映射,则返回null 。 如果此映射包含从键k到值
v的映射,使得 (key==null ? k==null : key.equals(k)) , 则此方法返回v ; 否则返回
null 。 (最多可以有一个这样的映射。) 返回值null不一定表示映射不包含键的映射; 
Map也有可能将键显式映射到null 。 containsKey(方法)操作可用于区分这两种情况。
返回哈希表中键对应的值。

下边结合源码来看一下具体的实现:

    /** 
     * 返回哈希表中key对应的value,如果此映射不包含键的映射,则返回null 。
     *
     * 如果此映射包含从键k到值v的映射,使得
     * (key==null ? k==null : key.equals(k)) ,
     * 则此方法返回v ; 否则返回null 。 (最多可以有一个这样的映射。)
     *
     * 返回值null不一定表示映射不包含键的映射;
     * Map也有可能将键显式映射到null 。
     * containsKey(方法)操作可用于区分这两种情况。返回哈希表中键对应的值
     *
     *
     */
    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

其实核心的方法还是 getNode:

final Node<K,V> getNode(int hash, Object key) {
        // tab 引用当前HashMap的散列表
        // first 桶位中的头元素
        // e 临时存储的元素
        // n 哈希表数组的长度
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // 如果哈希表不是空,并且长度大于0,表示哈希表已经初始化,或者已经有元素了,
        // (n - 1) & hash是计算当前key桶位的位置下标算法,first即当前key所在桶位的头元素
        // 如果头元素不是空的表示此桶位上有元素,再去进行取值操作
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            // 最简单的情况:定位出来的头元素就是key所处的元素,直接返回此元素
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 头元素不是key所在的元素,first.next != null表示当前桶位上不止一个元素,可能是链表或者红黑树
            if ((e = first.next) != null) {
                // 红黑树
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);

                // 链表结构
                do {
                    // 如果hash和key和节点的key一致则表示找到了对应的元素
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null); // 表示还有下一个节点,如果没有下一个节点,表示没有找到对应的key
            }
        }
        return null;
    }

remove方法源码解析

public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

remove方法也是通过调用一个核心的方法 removeNode 来做的

/**
     * Implements Map.remove and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value matchValue为true的情况下,key对应的value和此参数相等才会移除元素,否则忽略
     * @param matchValue 如果为真,则仅在值相等时删除
     * @param movable 如果为 false,则在删除时不移动其他节点
     * @return key所在的Node节点,如果没有,则为 null
     */
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {

        // tab 引用当前HashMap的散列表
        // p 传入key对应桶位的头元素
        // n 当前哈希表数组的长度
        // index key元素所在桶位下标
        Node<K,V>[] tab; Node<K,V> p; int n, index;

        // 哈希表不是空,且长度大于0,key所在的桶位元素不是空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {

            // node 传入key所在的节点
            // e 临时存储的节点
            Node<K,V> node = null, e; K k; V v;

            // 第一种情况:key所在桶位的头元素就是key所在的Node节点,赋值给node
            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 {
                        // 链表上找到了key所在的节点,结束循环
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        // 此处保持p的下一个元素一直是e,每循环一次,p,e同时指向各自下一个元素,此时p.next=node
                        p = e;
                    } while ((e = e.next) != null);
                }
            }

            // 找到了key所在的节点后,如果节点不是null,说明按照key找到了需要删除的元素
            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);
                // 此时的key所在的节点在头元素,
                // 则删除的就是头结点,将头元素的下一个节点作为第一个元素(不管是不是null)存储在对应的桶位
                else if (node == p)
                    tab[index] = node.next;
                // key所在元素不是第一个元素,此时的p.next = node,要删除的节点是node,
                // 所以p的下一个元素不是指向node,而是node的下一个元素,即p.next = node.next
                else
                    p.next = node.next;
                // 增加哈希表结构变化的 次数
                ++modCount;
                // 减少一个哈希表中的元素个数
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

repalce方法源码解析

replace方法就很简单的,直接上代码:

public V replace(K key, V value) {
        Node<K,V> e;
        // 获取key所在的节点,不为空进行value替换
        if ((e = getNode(hash(key), key)) != null) {
            // 旧值
            V oldValue = e.value;
            // 将新值赋给key所在节点的value
            e.value = value;
            afterNodeAccess(e);
            // 返回旧值
            return oldValue;
        }
        return null;
    }

\