JDK13-HashMap的putVal、getNode、removeNode源码分析

hashmap的四种构造方式

这篇文章有点长,建议看下去

1.输入初始容量和负载因子 new HashMap(15,0.5)
public HashMap(int initialCapacity, float loadFactor)

函数内容是

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
         //首先判断了输入数据的合理性,给定容量必须是正值
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
         //且小于最大容量
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
         //负载因子必须是一个大于0的数字
        this.loadFactor = loadFactor;//首先负载因子进行赋值
        this.threshold = tableSizeFor(initialCapacity);
    }

函数isNaN是Float类实现方法,用于判断一个值是否Not-a-Number。

如果以上都成立,调用tableSizeFor函数,产生一个大于给定容量的最小的2的某次方。

static final int tableSizeFor(int cap) {
        int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
//>>>带符号右移操作,用0补位
//>>不带符号右移,符号位为1则用1补位。

numberOfLeadingZeros函数是Integer类实现的,用于数一个数前面有多少位是0

public static int numberOfLeadingZeros(int i) {
        // HD, Count leading 0's
        if (i <= 0)
            return i == 0 ? 32 : 0;
        int n = 31;
        if (i >= 1 << 16) { n -= 16; i >>>= 16; }
        if (i >= 1 <<  8) { n -=  8; i >>>=  8; }
        if (i >= 1 <<  4) { n -=  4; i >>>=  4; }
        if (i >= 1 <<  2) { n -=  2; i >>>=  2; }
        return n - (i >>> 1);
    }

可以看出,以上函数在求i前面有多少位0(可以带一个数试一下)
cap从0开始:
cap=0时,i=-1,最高位是1,前面没有0,n=-1,threshold=1
cap=1时,i=0,有32位0,-1带符号右移32位等同于右移0位,n=-1,threshold=1
cap=2时,i=1,有31位0,-1带符号右移31位,变为0**01,n=1,threshold=2
cap=3时,i=2,有30位0,-1右移30位,变为0 ** 11,n=2,threshold=4
cap=5时,i=4,有29位0,-1右移29位,变为0 **111,n=7,threshold=8
(注意-1在32位二进制中表示为:11111111 11111111 11111111 11111111)

这里要注意的是HashMap并不是在产生新对象的时候就确定真实容量和阈值,而是在写入第一组键值对(Node)的时候才确定这个map的容量和阈值。上面求出来的threshold仅仅是一次赋值。
在写入map的时候都会判断容量并调用resize()函数。
resize()函数用于对哈希table进行初始化和扩容,在第一次写入Node时进行初始化,就是利用之前求出来的loadFactor和threshold。
resize函数的解析见:https://blog.csdn.net/weixin_44893585/article/details/103638977

2.输入一个初始容量构造

利用this关键字调用第一种构造方式

public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

3.无参构造方式

public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

无参构造方式会在table初始化的时候调用resize函数,并利用默认容量和负载因子进行初始化。

4.用一个字典来构造(用键值对来构造)
public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);//把字典加到HashMap里的方法
    }

putMapEntries是把字典加到HashMap里的方法

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        if (table == null) { //说明table还没初始化
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        } 
        else {//当s > threshold,需要重新散列resize
            while (s > threshold && table.length < MAXIMUM_CAPACITY)
                resize();
        }
        //table已经初始化
        //或者根据输入字典的长度重新散列之后
        //或者输入字典的s<threshold,可以直接放入
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);//调用最初的putVal函数进行put见下文
        }
    }
}

这里的hash(key)函数的内容是

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}//hashcode 和Hashcode带符号右移16位相异或,为的是减少冲突。

Hashcode的源码解析参考链接:
https://blog.csdn.net/weixin_44893585/article/details/103638755

putVal就是把Node放入哪个桶,更具体的,放到链表的哪个位置上的函数

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)//如果table为空
            n = (tab = resize()).length;//利用resize进行初始化,并将初始化后的Node数组的长度赋值给n
        if ((p = tab[i = (n - 1) & hash]) == null)//(n - 1) & hash = hash%n,是一种高效的取余运算方法,方便求索引(放入哪个桶)
            tab[i] = newNode(hash, key, value, null);//如果是空桶,就放一个节点,且next=null
        else {//说明不是空桶
            HashMap.Node<K,V> e; K k;
            if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))//要put的Node和原有的Node是相同的
                e = p;//e就指向这个桶
            else if (p instanceof HashMap.TreeNode)
                e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//与原有的Node不相同
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);//p.next为空,直接放进去,e指向此桶
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);//达到树化阈值,链表变成红黑树
                        break;
                    }
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        break;//如果与某个p.next值相同,说明不用再放一次了
                    p = e;
                }
            }//上面还没有放入新的Node,只是确定了存放位置
            if (e != null) { //e引用刚才确定好的Node节点
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)//onlyIfAbsent if true, don't change existing value只有值是false,才会对改动原来的Node的值
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;//The number of times this HashMap has been structurally modified
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);//void afterNodeInsertion(boolean evict) { }没有执行任何操作
        return null;
    }

afterNodeAccess(e);这一句没有执行操作
afterNodeInsertion(evict);这一句也没有执行任何操作

void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }

其实这两个函数是为了HashMap的子类LinkedHashMap准备的,此处无具体执行内容

getNode函数

final HashMap.Node<K,V> getNode(int hash, Object key) {//返回一个Node
        HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            //要去索引对应的桶里,从第一个节点开始比较
            if ((e = first.next) != null) {
                if (first instanceof HashMap.TreeNode)
                    return ((HashMap.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;
    }

getNode函数如果找到了就返回找到的那个节点,没找到则返回null

removeNode函数

final HashMap.Node<K,V> removeNode(int hash, Object key, Object value,
        boolean matchValue, boolean movable) {//返回一个Node节点
    HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
                (p = tab[index = (n - 1) & hash]) != null) 
    {   HashMap.Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof HashMap.TreeNode)
                    node = ((HashMap.TreeNode<K,V>)p).getTreeNode(hash, key);
        else{
                do {
                    if (e.hash == hash &&
                       ((k = e.key) == key ||
                                        (key != null && key.equals(k)))) {
                        node = e;//当找到的时候会把e.next赋给node
                        break;//并结束循环,不给p赋值,所以p指向待删节点的前节点
                    }
                    p = e;//不管有没有执行if,都会把e.next赋给p
                } while ((e = e.next) != null);
            }
        }//将匹配到要删除的节点赋值给p,node
        if (node != null && (!matchValue || (v = node.value) == value ||
                    (value != null && value.equals(v)))) {
            if (node instanceof HashMap.TreeNode)
                    ((HashMap.TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p)//说明要删除的Node是头节点
                    tab[index] = node.next;
            else //要删除的Node不是头节点
                p.next = node.next;//将目标节点的前驱元的后继元指向目标节点的后继元,则node节点被略过
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
        }
        return null;
    }
原创文章 64 获赞 27 访问量 9437

猜你喜欢

转载自blog.csdn.net/weixin_44893585/article/details/103638927