jdk 1.8 hashmap源码解读(详细)(上)

来源:互联网 发布:js array splice无参数 编辑:程序博客网 时间:2024/05/21 17:04

package java.util;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
    /**
     * 默认的初始化大小16
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     *
     * 最大的容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默认的加载因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     *使用树计算的阀值 8  (当通上的节点数大于8转成树)
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 不使用树的阀值 6 (桶节点小于6转化成链表)
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 当为最小的树的时候数组容量为64
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /**
     * 内部类实现map.entry<k,v>结构,可以看做基本单元,一个节点
     */
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash; //节点属性哈希值
        final K key;
        V value;
        Node<K,V> next; //指向下一个节点

        
        //节点初始化 构造函数
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }
        ///节点的哈希值算法,key和value哈希取异或 ,因此节点本身的hashCode和key和value都有关系
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
        ///设置值,返回就值
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        //节点是否相等,key相等,value相等
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue())) ///多了个判断不为null
                    return true;
            }
            return false;
        }
    }

    /* ---------------- Static utilities -------------- */

    
    
    ///hash计算,可以看出16位有效位
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); ///32位的,把高位和低位异或,使高位参与运算,分布更均匀,避免碰撞
    }

    //获取对象的Comparable<>的泛型的string.class
    static Class<?> comparableClassFor(Object x) {
        if (x instanceof Comparable) {  //判断是否实现 比较接口
            Class<?> c;
            Type[] ts, as;
            Type t;
            ParameterizedType p;
            if ((c = x.getClass()) == String.class) // bypass checks   说明:这里有两步 1.赋值c,2.判断string.class
                return c;  //如果是string类型,返回string.class
            if ((ts = c.getGenericInterfaces()) != null) {  //c对象实现的所有接口,如果是接口,泛型的原型
                for (int i = 0; i < ts.length; ++i) {
                    if (((t = ts[i]) instanceof ParameterizedType) &&//判断是否包含接口
                        ((p = (ParameterizedType)t).getRawType() ==
                         Comparable.class) &&//此接口是否是Comparable接口
                        (as = p.getActualTypeArguments()) != null &&
                        as.length == 1 && as[0] == c) // 判断第一个类型是不是String.class类型的
                        return c;
                }
            }
        }
        return null;
    }

    /**
     * x的Class文件为kc,且不为null,k>x 1,等0,小-1
     */
    @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
    static int compareComparables(Class<?> kc, Object k, Object x) {
        return (x == null || x.getClass() != kc ? 0 :
                ((Comparable)k).compareTo(x));  //如果k>x 为1 等为0 小-1
    }

    /**
     * 返回比给定整数大且最接近的2的幂次方整数,如10,返回16
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

    /* ---------------- Fields -------------- */

    
     ///节点的数组,长度2的次数
    transient Node<K,V>[] table;

    /**
     * 存放具体的键值对的set集合
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * 实际存在的键值对的大小.
     */
    transient int size;

    /**
     * 改变的次数
     */
    transient int modCount;

    /**
     *
     *临界阀值扩容:容量*加载因子(下次改变的大小)
     *
     */
    int threshold;

    /**
     *
     * 对于数组的加载因子
     */
    final float loadFactor;

    /* ---------------- Public operations -------------- */

    /**
     * 初始化,构造函数:阀值大小   加载因子(浮点级)
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)///初始容量小于0 报错
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY; //大于最大取最大,2的31次方,大约21亿
        if (loadFactor <= 0 || Float.isNaN(loadFactor))////加载因子小于0,或者不是数字 报错
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;  
        this.threshold = tableSizeFor(initialCapacity); //初始化阀值的大小
    }

    /**
     *
     *
     * 构造函数:加载因子取默认0.75,大小输入
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * 构造函数:加载因子0.75
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     *
     * 以map<entry<,>>为初始化的构造函数
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

    /**
     *将map中的值加入本hashmap中
     *

     */
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size(); ///加入map的大小
        if (s > 0) {        
            if (table == null) { // pre-size  ///刚开始的时候note[]为null
                float ft = ((float)s / loadFactor) + 1.0F;   /////////////////////////由加载因子和实际大小的值推出应该赋予的真实容量,
                int t = ((ft < (float)MAXIMUM_CAPACITY) ? //////得到值小于最大值,取得到值,大于最大,取最大    ////继上:这里说明+1.0F的作用,是为了在下一步(int)转型的时候小数
                         (int)ft : MAXIMUM_CAPACITY);       ////继上:去掉问题的解决,如果不加,就会算出值小于真实容量,反过来即阀值小于加入的map大小
                if (t > threshold) //需要容量大于设置阀值 :意味着放入map,map的值达到了扩容的阀值,即是放入的map大小比设置的map阀值大(思想:为上步的需求计算的容错)
                    threshold = tableSizeFor(t);//调大阀值,为可能出现的扩容做准备,即是初始化阀值
            }
            
            
            else if (s > threshold) ///如果放入的table不为null,且放入的map实际值大于当前的阀值    
                resize(); ///扩容,把已有的分布到新表并扩容 设置阀值
            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);//循环放入 存在数组的首链替换,链中不替换
            }
        }
    }

    /**
     *返回大小
     *
     */
    public int size() {
        return size;
    }

    /**
     * 是否为null长度为0
     *
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 由key获得值,先获得节点,在获得值
     */
    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    /**
     *
     *查找所需要的节点
     * 第一步:判断数组的首连接
     * 第二步:如果首连接符合,返回首连接,不符合,判断下个连接是否为null,
     *为null返回null,不为null,判断是否首连接是否是树连接,
     *是:查找树,不是:查找从首连接的下一个开始遍历查找
     */
    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) { ////在现有数组不为null和长度大于0前提下,得到坐标的首连接不为null
            if (first.hash == hash && ///查看首连接是否是符合要求的的节点(hash值和key值)
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first; //符合返回
            if ((e = first.next) != null) { ///首连接下一个不符合 包含把首连接下一个赋予e
                if (first instanceof TreeNode)  //判断首连接是否是树连接
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key); //从树中hash和key获取值
                do {
                    if (e.hash == hash && //如果不是tree遍历连接的每个节点
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

    /**
     *
     *
     * 是否包含:查找,返回null,为true包含,返回值,为false,不包含
     */
    public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }

    /**
     *                
     * 放入key,和value         
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);///放入,false的时候,在onlyifabsent取fasle的时候默认数组首位置替换,支持linkedhashmap的回调
    }

    /**
     *
     * 加入值
     *嗯,代码有点小长,总结下:
     *第一步:判断hashmap数组是否为null,是:初始化。
     *第二步:找到数组下标的首节点,为null加入,
     *第三步,不为null,符合hash和key相等条件,取出当前节点,在最后判断当输入的条件为true和不为null时,不替换,其他替换,在最后设置linkedhashmap回调
     *第四步:不为null,不符合条件,判断是否是树节点,是加入
     *第五步:不为null,首节点不符合条件,不是树节点,只有是链节点,for循环查找,下节点为null,加入下节点,当加入长度达到7,转换成树,如果已经存在不加入
     *第六部:改变变量加一,判断是否达到阀值和回调
     *注意,当加入的值在数组的链的首位置的时候当输入值true和不为空的时候不进行替换,但是当为链的其他位置的时候已经存在key值value不替换,value
     *
     */                                              a.为false替换  b.为true当null才替换
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
       
          Node<K,V>[] tab; ////节点数组
        Node<K,V> p;//一个节点
        int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)//当前现有的节点为null或者0 这里包含把现有数组赋予tab和数组长度赋予了n
            n = (tab = resize()).length; //旧节点为null,对其初始化 容量为16的数组
        if ((p = tab[i = (n - 1) & hash]) == null) //如果当前插入位置值为null  p=传入当前的值 (第一个note的对象)
            tab[i] = newNode(hash, key, value, null);//在当前节点创建node,
        else { //如果有值
            Node<K,V> e; K k;
            if (p.hash == hash &&  //把传入的hash和hash对比  在相等的前提下key值进行对比相等
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;//取出当前值 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
            else if (p instanceof TreeNode)//当hash和key比较失败的时候,判断是否为红黑树
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//8放入红黑树
            else { //链首位的hash和key不服和,也不符合树节点,只有在链的其他位置
                for (int binCount = 0; ; ++binCount) { //无限循环查找 ///注意当多线程的情况下,这里容易形成死循环,cpu爆
                    if ((e = p.next) == null) { //把首节点的下一个节点指向e,如果为null
                        p.next = newNode(hash, key, value, null); 创建新节点,并把它指向p首节点
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//如果本链添加的长度大于等于TREEIFY_THRESHOLD-1=7
                            treeifyBin(tab, hash);////********
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) ///如果已经存在,不加入
                        break;
                    p = e; //便于下次循环传递
                }
            }
            if (e != null) { // existing mapping for key  ///在数组的首节点相等时候,对应上面的XXXXX所在行
                V oldValue = e.value;  //取出旧值
                if (!onlyIfAbsent || oldValue == null)//旧值为null或者输入条件为false时 进行新旧值替换
                    e.value = value;
                afterNodeAccess(e); ///当为linkedhashmap时候许可回调
                return oldValue;  //返回旧值
            }
        }
        ++modCount; //改变加一
        if (++size > threshold) ///实际值大小大于阀值
            resize(); 扩容
        afterNodeInsertion(evict); //当为linkedhashmap时候许可回调
        return null;返回null
    }

    /**
     *
     * 扩容机制,扩容
     *
     *嗯,有点长,总结下:
     *第一步 A:判断判断是否存在旧值,如果旧结点达最大,就让他大吧!如果没有达到最大,
     *容量扩大2倍,阀值扩大2倍。B:判断没有存在旧值,判断那一步初始化阶段,没有内容,有没阀值,
     *然后if对阀值补充,将最新阀值赋予当前阀值.
     *第二步:A.赋值并释放当前数组.B如果单节点,直接均匀放入C.如果不是单节点是树节点,
     *放入树节点D.如果不是单节点也不是树节点,把当前的链分两条分别放入对应新表两部分对应下标(下标之差为旧表的数组大小)
     */
    final Node<K,V>[] resize() {   
        Node<K,V>[] oldTab = table; //取出现有接点数组
        int oldCap = (oldTab == null) ? 0 : oldTab.length;///获取旧节点的长度  刚刚掉用构造函数的时候,没有值,大小取0
        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  //新的阀值取旧的2倍
        }    ///不存在旧结点
        else if (oldThr > 0) // 初始化,没有内容,但是设置阀值的情况 hashmap(inintcap,factoy)和hashmap(initcap)情况
            newCap = oldThr; 新的阀值取旧阀值
        else {               // 初始化,没有内容,也没有阀值的情况 hashmap()和hashmap(map)准备
            newCap = DEFAULT_INITIAL_CAPACITY; //默认容量16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //默认阀值16*0.75=12
        }
      ///新阀值为0    (什么时候出现?在上面的初始化,没有内容却又阀值)   
       if (newThr == 0) {
            float ft = (float)newCap * loadFactor; //在上面newCap是旧设置阀值*加载因子 扩容变小了??????  >_<
            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;//释放旧节点数组中的值,便于GC回收,把值赋予e
                    if (e.next == null)//如果当前就节点下个节点为空,即单节点
                        newTab[e.hash & (newCap - 1)] = e;///把e放入新数组中,下标以e的hash和容量-1与为下标
                        
                        ///https://www.zhihu.com/question/44460053  ///相当于对值的取膜
                        ///http://blog.csdn.net/zccracker/article/details/54378852 《--大力推荐文章   
                    else if (e instanceof TreeNode)//如果当前下个不为null,并为树节点,
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); ////额......>_< 把当前的节点 在新表中以j分成散列度的oldCap大小数,
                    else { // preserve order //下个节点不为null,不是树节点
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            ///e的hash和就oldCap是否为0与分割成两张链表。理解:oldCap的为16时10000,在以后每次2倍相当于向前移一位,都是100xxxx格式,取&是否为0的概率就变成对半分了
                            if ((e.hash & oldCap) == 0) {  
                                if (loTail == null) //当前为null,给当前,否则给下一个
                                    loHead = e;//
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)//当前为null,给当前,否则给下一个
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);//知道e的下一个为null为止,单向链表结构停止
                        if (loTail != null) {//当前节点不为null
                            loTail.next = null;//下个为null,(意味着放完了)
                            newTab[j] = loHead; //把当前链放入数组中
                        }
                        if (hiTail != null) {//当前节点不为null
                            hiTail.next = null;下个为null,//(意味着放完了)
                            newTab[j + oldCap] = hiHead;//把当前链放入数组中     这里把新的链放入之中,正好对应新的表大小为原来的两倍
                        }
                    }
                }
            }
        }
        return newTab;
    }

    /**
     * 当到链的长度达到7的时候变成树
     */
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)///如果为数组为null,或者数组的长度小于最小树容量64,扩容
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) { ///如果数组的长度达到就转化成节点树结构
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null); ///替换成树节点,从第一个开始
                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);
        }
    }

    /**
     * 将map放入hashmap中 许可启用linkedhashmap
     *
     */
    public void putAll(Map<? extends K, ? extends V> m) {
        putMapEntries(m, true);
    }

    /**
     * 由key来删除节点值,
     */
    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?  //含有操作,调用元素,取出节点,得到节点的值
            null : e.value;
    }

    /**
     *移除节点,有点多,总结下
     *第一步:判断当前hash含有链,然后判断首节点,首节点不符合,
     *判断下个节点是不是树,是树节点转到树,由树去取出节点,不是树,while检查链组
     *第二步:得到要删除的节点,判断输入匹配条件,
     *然后判断是否树,是由树去删除,不是,判断是否为首节点,是将要删的节点下个接点方到数组中
     *不是首节点,将上接点的下一个指向下节点的上一个,返回删除节点
     */
    final Node<K,V> removeNode(int hash, Object key, Object value, ///存在,只要值相等一定删
                               boolean matchValue, boolean movable) {
                               //如果false 不管等不等都删   // 树节点的移除标记         
       Node<K,V>[] tab; Node<K,V> p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) { //当前数组不为null,长度大于0,找到位置存在元素
            Node<K,V> node = null, e; K k; V v;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))) //检查第一个节点,赋予node
                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;   ///标记:BBBBBBBBBB
                    } while ((e = e.next) != null);
                }
            }
            ///在这一步把获取到的节点在node上
            if (node != null && (!matchValue || (v = node.value) == value || ///在删除的节点不为null的前提下,当
                                 (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; //是一般节点 把要删除的节点的上个节点指向要删节点的下一节点 注意:要和上面查找删除节点的判断结合一起看,BBBBBBB行
                ++modCount; //修改加一
                --size; //大小减一
                afterNodeRemoval(node); //linkedhashmap的回调
                return node; 返回要删节点
            }
        } //没有找到删除节点,(数组所在index链为null)
        return null;
    }

    /**
     * 清除,存在值,把size为0,把数组每个变成null有GC回收
     */
    public void clear() {
        Node<K,V>[] tab;
        modCount++;
        if ((tab = table) != null && size > 0) {
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }

    /**
     *
     * 遍历节点比较value是否包含
     */
    public boolean containsValue(Object value) {
        Node<K,V>[] tab; V v;
        if ((tab = table) != null && size > 0) {
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                    if ((v = e.value) == value ||
                        (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
    }