HashMap

好戏开演


重难点的HashMap源码解读  还有ConcurrentHashmap对比

HashMap核心源码解读
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    
    //默认的初始化容量 1*2*2*2*2=16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4
    //最大容量限制 2的30次方
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //默认负载因子 0.75
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //链表转化为树的临界值
    static final int TREEIFY_THRESHOLD = 8;
    //树退化成链表的临界值
    static final int UNTREEIFY_THRESHOLD = 6;
    //链表转化为树的阙值,只有当链表长度超过8并且整个数组的长度超过这个,才会将链表转化为红黑树
    static final int MIN_TREEIFY_CAPACITY = 64;
    
    //存放数值的结点
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        //结点key
        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; }
        //计算hashCOde的方法 调用object的方法根据key以及value进行计算
        public final int hashCode() {
            // ^是异或运算符  将运算符两边都写成二进制,然后比较每一位的数,相同取0 不同取1 得到的结果就是运算后的结果
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
    
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        //先判断传入的对象是否是本对象,  其次判断是否是Map.Entry类型 最后再比较他们的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()))
                    return true;
            }
            return false;
        }
    }
    
    //计算hash值
    static final int hash(Object key) {
        int h;
        //当传入的key不是null的时候 1 计算出key的hashCode值赋值给h 2 h右移16位 3 用h与2得到的结果进行异或运算
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    //hash表,存放值的数组
    transient Node<K,V>[] table;
    //后续完善这个东西的作用
    transient Set<Map.Entry<K,V>> entrySet;
    //map的大小
    transient int size;
    //map的修改次数
    transient int modCount;
    //要调整大小的下一个大小值(容量*负载系数) 要扩容的临界值 当map容量达到这个之后,就进行扩容
    int threshold;
    //负载因子
    final float loadFactor;
    
    //三个构造函数
    public HashMap() {
        //初始化负载因子
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    
    public HashMap(int initialCapacity) {
        //调用两个参数的构造函数
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
    public HashMap(int initialCapacity, float loadFactor) {
        //初始化容量为0 直接抛异常
        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);
        //初始化负载因子
        this.loadFactor = loadFactor;
        //设置扩容临界值
        this.threshold = tableSizeFor(initialCapacity);
    }
    
    //好戏开演~~~~~~
    
    //给map添加数据
    public V put(K key, V value) {
        //计算出key的hash值,然后调用下面方法存值
        return putVal(hash(key), key, value, false, true);
    }
    //真正存放值的方法
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        //临时数组        临时节点
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //将map中的表赋值给tab 并进行非空判断
        if ((tab = table) == null || (n = tab.length) == 0)
            //当表为空,进行扩容
            n = (tab = resize()).length;
        //计算将要放入数组的索引   用hash表的长度-1与上一步计算出来的key的hash值做与运算
        if ((p = tab[i = (n - 1) & hash]) == null)
            //当前hash表的位置没有值,那么创建新的结点,并将其放在当前索引位置
            tab[i] = newNode(hash, key, value, null);
        else {
            //当前索引存在值,也就是已经发生了hash冲突
            Node<K,V> e; K k;
            //判断需要存入的值的key与hash表当前索引的存放的结点的key是不是相等
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                //相等就是要将key的值替换为新的, 那么将这个结点保存在e中 后面做value的替换
                e = p;
            //当前结点的key与需要放入map的key不相等,首先判断这个结点是不是数结构
            else if (p instanceof TreeNode)
                //树节点,那么进行树结点的添加操作
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //当前结点时链表结构
                for (int binCount = 0; ; ++binCount) {
                    //判断当前节点是否存在下一个结点,因为hash冲突之后,是以链表形式存放数据,所以每一个结点都有指向下一个结点的指针
                    if ((e = p.next) == null) {
                        //不存在下一个结点,那么直接创建新的结点,并将其添加到当前节点的后面
                        p.next = newNode(hash, key, value, null);
                        //判断当前节点的长度是不是大于等于8  是的话,需要将链表转化成红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //将链表转化为红黑树
                            treeifyBin(tab, hash);
                        break;
                    }
                    //当前结点存在下一个结点,那么判断下一个结点的key与需要放入map的key是不是同样的
                    //简单来说,就是需要判断放入map的数据的key是不是已经在这个链表中。所以需要将这个key与当前链表的所有节点的key进行比较
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        //如果key恰好在当前的链表中,跳出循环 此时,e结点的key与当前需要加入map的key是同一个key
                        break;
                    //将p指向下一个结点 继续循环判断
                    p = e;
                }
            }
            //能走到这一步,说明key已经存在与链表中,并且已经将链表中的结点取出保存在了e节点中
            if (e != null) { // existing mapping for key
                //获取结点的旧值
                V oldValue = e.value;
                //判断是否允许替换,或者旧值为null
                if (!onlyIfAbsent || oldValue == null)
                    //将结点的值替换为新的值
                    e.value = value;
                afterNodeAccess(e);
                //返回结点旧值
                return oldValue;
            }
        }
        //当新添加数据之后,将map的修改次数+1
        ++modCount;
        //map的大小+1,并且判断map的大小是否超过扩容阙值
        if (++size > threshold)
            //超过阙值,进行扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    
    //hash表扩容
    final Node<K,V>[] resize() {
        //获取旧的hash表
        Node<K,V>[] oldTab = table;
        //获取旧表的长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //获取扩容临界值
        int oldThr = threshold;
        //新表长度   新表的扩容临界值
        int newCap, newThr = 0;
        //旧表长度大于0
        if (oldCap > 0) {
            //旧表长度大于最大的容量
            if (oldCap >= MAXIMUM_CAPACITY) {
                //将旧表扩容临界值改为最大的Integer数
                threshold = Integer.MAX_VALUE;
                //直接返回旧表
                return oldTab;
            }
            //将旧表长度*2  作为新表的长度  并判断是否小于最大容量 并且旧表长度是否大于等于默认的初始化容量(16)
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
                //设置新表的扩容临界值为旧表扩容临界值*2
                newThr = oldThr << 1; // double threshold
        }
        //旧表扩容临界值>0  将旧表的扩容值设置为新表的长度
        else if (oldThr > 0) // initial capacity was placed in threshold
            //设置新表的长度
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            //否则,第一次添加数据  初始化新表表为默认的容量  也就是数组长度为默认的16
            newCap = DEFAULT_INITIAL_CAPACITY;
            //初始化新表的扩容的临界值 默认的负载因子*默认的容量  0.75*16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //当前面判断完都没设置新表扩容值的时候  就在这里初始化这个
        if (newThr == 0) {
            //计算 新表长度*负载因子
            float ft = (float)newCap * loadFactor;
            //新表长度小于最大容量  设置新表扩容值为上面计算的值
            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) {
                    //表的当前索引位置值不位null 将其设置为null
                    oldTab[j] = null;
                    //精华部分: 判断这个结点的下一个结点是否为null  
                    //解释一波:因为hash表是数组加链表的形式 我们当前是遍历的数据,拿到数据指定索引的值 然而,这个node结点还有关联的next结点,他是node之间的关联关系  与数组没有任何关系哦
                    //就相当于你有5支铅笔,铅笔顺序排列着,但是每一支铅笔还绑定了自己的橡皮、铅笔刀等  所以这一块的next就类似于与当前铅笔绑定的橡皮、铅笔刀。
                    if (e.next == null)
                        //next为null,将当前key的hash值与新容量-1的值做与运行算,得到的结果作为索引,将当前结点放置到新表中
                        newTab[e.hash & (newCap - 1)] = e;
                    //next结点不是null ,判断当前e是不是树结构
                    else if (e instanceof TreeNode)
                        //树结构,那么使用树的方式将以前的数据放入新的表中  有点复杂,后续完善这个
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        //是链表  总结:将hash值与旧容量做与运算为0的结点挂在到新链表的j+oldCap位置 否则,挂在到原位置
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            //获取下一个节点
                            next = e.next;
                            //如果e的hash值与旧容量做与运算得到的结果为0
                            if ((e.hash & oldCap) == 0) {
                                //如果loTail没有值,
                                if (loTail == null)
                                    //将e赋值给loHead  存储链表头结点
                                    loHead = e;
                                else
                                    //loTail有值
                                    loTail.next = e;
                                //将e复制给loTail
                                loTail = e;
                            }
                            //这个else的操作同上 
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                          //循环,直至找到链表最后的结点
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            //将loHead挂在到新的表的j索引位置
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            //将hiHead挂在到j+旧容量为索引的位置
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        //返回新表
        return newTab;
    }
    
    //树节点的添加操作    139行代码详解 map:本对象  tab:hash表存放值的数组  h:key的hash值  k:键 v:value
    final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v) {
        Class<?> kc = null;
        //判断子树的每个结点的key是否与传入的key相同的条件  根节点的时候判断一次旧ok 否则每一个结点的子树都需要判断
        boolean searched = false;
        //获取树的根节点
        TreeNode<K,V> root = (parent != null) ? root() : this;
        //遍历树
        for (TreeNode<K,V> p = root;;) {
            int dir, ph; K pk;
            //当前树节点的hash值>将要存入map的k的hash值
            if ((ph = p.hash) > h)
                dir = -1;
            //当前树节点的hash值<将要存入map的k的hash值
            else if (ph < h)
                dir = 1;
            //两个hash值相等,判断当前结点的key与需要放入map的key是不是相同
            else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                //相同的话,直接返回改结点,然后做值的替换
                return p;
            //不是用一个key 并且k没有实现compareComparable接口,
            else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) {
                if (!searched) {
                    TreeNode<K,V> q, ch;
                    searched = true;
                    //查询改树的左子树与右子树,判断有没有结点的key与当前需要加入map的key相同  有的话,直接返回改结点
                    if (((ch = p.left) != null &&
                         (q = ch.find(h, k, kc)) != null) ||
                        ((ch = p.right) != null &&
                         (q = ch.find(h, k, kc)) != null))
                        return q;
                }
                //将当前结点的key与需要加入map的key进行排序
                dir = tieBreakOrder(k, pk);
            }
            //当前遍历的结点
            TreeNode<K,V> xp = p;
            //找到需要插入的树是当前树的左子树还是右子树,并且左子树||右子树结点没有值
            if ((p = (dir <= 0) ? p.left : p.right) == null) {
                //临时保存当前遍历节点的下一个结点
                Node<K,V> xpn = xp.next;
                //创建一个新的树结点
                TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                if (dir <= 0)
                    //将新节点挂到当前遍历的树节点上
                    xp.left = x;
                else
                    xp.right = x;
                //将新节点挂载到当前遍历的node护额短板上岗
                xp.next = x;
                //设置新节点的父节点以及上一个结点 这里保存了双向链表的特征,同时又有树的特征
                x.parent = x.prev = xp;
                //临时保存的下一个结点不为null。
                if (xpn != null)
                    //将临时保存的结点的上一个结点设置为新创建的结点x
                    ((TreeNode<K,V>)xpn).prev = x;
                //树中添加了新的结点,进行树的调整,以保持树的平衡,并且设置根节点为改索引位置的第一个结点
                moveRootToFront(tab, balanceInsertion(root, x));
                return null;
            }
        }
    }
    
    //151行代码详解  链表转化为红黑树
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //如果hash表为null或者表的长度<最小树化的阙值,那么就进行扩容 而不是说链表大于等于8就树化
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        //再次确认当前索引的确有hash冲突
        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);
            //再次确认head结点不为null,样子并且将其放入表的当前索引位置
            if ((tab[index] = hd) != null)
                //树化  也就是将一个链表转化为树,这一块就跟java没关系了   无非就是判断结点值与根节点的大小,然后插入到根节点的子节点中而已
                hd.treeify(tab);
        }
    }
    
    //252详解 这一块会牵扯到树退化与进化问题
    //在hashmap扩容之后,会将以前的数据移到新表中 这一块会牵扯hashmap的树的退化问题
    //                     本对象 hashmap 新的hash表 转移数据的索引 旧hash表的容量
    final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
        //hash表需要转移到新表的索引上的第一个节点   因为需要将这个索引的数据全部移到新表中
        TreeNode<K,V> b = this;
        // Relink into lo and hi lists, preserving order
        TreeNode<K,V> loHead = null, loTail = null;
        TreeNode<K,V> hiHead = null, hiTail = null;
        int lc = 0, hc = 0;
        //循环遍历这个索引上的全部结点
        for (TreeNode<K,V> e = b, next; e != null; e = next) {
            //临时保存下一个结点
            next = (TreeNode<K,V>)e.next;
            //将遍历中的这个节点的下一个结点置为空
            e.next = null;
            //计算是高结点还是低结点   简单来说,就是将这个树分为两部分而已
            if ((e.hash & bit) == 0) {
                //设置loHead ,并且挂在上lotail 
                if ((e.prev = loTail) == null)
                    loHead = e;
                else
                    loTail.next = e;
                loTail = e;
                //记录数据数量,用来判断是否需要进行链表化
                ++lc;
            }
            else {
                //同上面 设置hiHead  挂在上hiTail
                if ((e.prev = hiTail) == null)
                    hiHead = e;
                else
                    hiTail.next = e;
                hiTail = e;
                ++hc;
            }
        }
        //loHead不为空
        if (loHead != null) {
            //如果loHead中的节点数小于等于树退化的临界值
            if (lc <= UNTREEIFY_THRESHOLD)
                //进行树的退化
                tab[index] = loHead.untreeify(map);
            else {
                //进入else  说明loHead中挂在的结点数量>8  但是这个不一定是树结构  
                tab[index] = loHead;
                //走到这一步,说明树结构已经被破坏
                //因为在上面进行高低结点拆分的时候,已经破坏了树的结构 
                //极限情况下, 这个结点全部都是高结点  或者全部都是低结点  那么这个树结构不会变
                //但是现在既有loHead,也有hiHead,说明树结构被破坏了
                if (hiHead != null) // (else is already treeified)
                    //将loHead进行树化 
                    loHead.treeify(tab);
            }
        }
        //这里的判断跟上面loHead相同
        if (hiHead != null) {
            if (hc <= UNTREEIFY_THRESHOLD)
                tab[index + bit] = hiHead.untreeify(map);
            else {
                tab[index + bit] = hiHead;
                if (loHead != null)
                    hiHead.treeify(tab);
            }
        }
    }
    
    //移除指定的key
    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value;
    }
    
    //移除key为指定key的结点
    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;
        //hash表不为空 并且根据传入的key计算出来的表的索引位置存在结点
        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;
            //如果当前索引的第一个结点的key就是我们需要移除的key
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                //将这个结点保存在临时节点中
                node = p;
            //当前索引存放的结点存在下一个结点
            else if ((e = p.next) != null) {
                //p结点是树结构
                if (p instanceof TreeNode)
                    //从树中查找key为我们需要移除的结点
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    //p结点是链表
                    do {
                        //p的下一个结点e是我们需要移除的结点
                        if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
                            //将结点保存在临时节点中
                            node = e;
                            break;
                        }
                        //e不是我们需要移除的结点,指针后移,遍历后面的结点
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            //如果查到了我们需要移除的结点
            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也在往后移,此时p是我们需要移除的结点的前结点
                    //直接将p的下一结点指向我们需要移除的节点的下一个结点  也就相当于移除了此节点
                    p.next = node.next;
                //hash表的修改次数+1
                ++modCount;
                //hash表的长度-1
                --size;
                afterNodeRemoval(node);
                //返回移除的结点
                return node;
            }
        }
        return null;
    }

 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值