HashMap源码阅读

针对HashMap源码阅读做一个记录,关于HashMap的结构图网上有很多,就懒得画了,想看的时候直接网上搜索。
hashmap类图


图片.png

hashmap的部分公共方法


图片.png

hashMap源码的可读性相比其他框架代码来说不是很好,读起来有一些不舒服,本文没有对红黑树进行讲解,因为具体对红黑树也不是很了解,只知道其大体结构不知道怎么去实现,等有时间需要补补红黑树的实现。

属性

    //默认大小
    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;
    //链表大小这个变成红黑树
    static final int TREEIFY_THRESHOLD = 8;
    //红黑树小于这个变成链表
    static final int UNTREEIFY_THRESHOLD = 6;

    static final int MIN_TREEIFY_CAPACITY = 64;
    //存储Node的数组
    transient Node[] table;
    //一种存储map 中key value的数据结构,执行entrySet会返回这个
    transient Set> entrySet;

    transient int size;
    //存储结点数操作
    transient int modCount;
    //当size大于threshold执行扩容
    int threshold;

    final float loadFactor;

基本数据类型

HashMap中用于存储key-value的数据结构是静态内部类Node,下面是Node代码,比较简单

static class Node implements Map.Entry {
        //hash
        final int hash;
        //key
        final K key;
        //value
        V value;
        Node next;

        Node(int hash, K key, V value, Node 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; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        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;
        }
    }

因为HashMap新增了红黑树来进行存储,下面是红黑树的基本属性(方法已经省略),这里不对红黑树进行探索(因为不会),//todo 等后面会了在进行补充

static final class TreeNode extends LinkedHashMap.Entry {
        TreeNode parent;  // red-black tree links
        TreeNode left;
        TreeNode right;
        TreeNode prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node next) {
            super(hash, key, val, next);
        }
}

构造函数

     //返回输入数最近的2的次幂,比如 输入30返回32,返回的数>=输入的数
    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;
    }
    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);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

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

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

    public HashMap(Map m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
    //这里相当于向HashMap中存储Map,
    final void putMapEntries(Map m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size
                //计算扩容size,+1.0是因为计算是float,转化为int会自动丢失小数点后数字
                float ft = ((float)s / loadFactor) + 1.0F;
                //大小为t
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                //设置最大大小threshold
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            //因为这里不止初始化的时候使用,还有可能出现putAll中也会调用这个方法,当传入数据的map值大于当前最大threshold直接扩容后添加,关于当前数组容量+s的值大于threshold的问题这里没处理,是因为putVal的时候也会扩容
            else if (s > threshold)
                //扩容,后面讲
                resize();
            for (Map.Entry e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                //添加值,后面讲解
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

tableSizeFor中算法解析
获取离该数最近的2的幂次方数有规律,例如
110, 11 , 1011, 1000这四个数,距离他们最大的2的幂次方数分别为
1000,100,10000, 1000这四个数,即在前面补1,其余位全部置0,本身就是2的幂次方数除外。
换而言之我们只需要得出111, 11, 1111, 111这四个数,然后将他们+1即可。
而如何得到最高位只需要将原来的数-1,然后依次向右移动 与原本自身‘|’就可以了,因为只需要最高位的数字,只要最高位为1,向右移动然后相或后面所有位置都会为1,结果最高位后必然全是1,最终结果+1即为所得答案。

put

    static final int hash(Object key) {
        int h;
        //key等于null ,hash为0, 不为null取hash值和自身的hash向右移动16为进行异或
        //>>>表示无符号右移,高位补0,^表示异或,同值取0,异值取1,所以h的高16位不变,至于为什么这样用是因为这样可以让key更加均匀的分布,在后面代码中会进行分析。
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods
     *
     *   这里是核心代码
     * @param hash key的hash
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent 如果存在就不更改值
     * @param evict 这个参数留给子类扩展的,参考LinkedHashMap
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        //tab为table[], n为table[]长度,i表示key对应的table下标索引,p表示对应下标的第一个数据
        Node[] tab; Node p; int n, i;
        //当map中没有数据执行这里
        if ((tab = table) == null || (n = tab.length) == 0)
            //返回新存储数据数组的大小
            n = (tab = resize()).length;
        //(n - 1) & hash 数组长度和key的hash相或,用来获得数据应该存入数组的下标,这里就是为什么hashMap中的size大小为2的幂的原因。
        if ((p = tab[i = (n - 1) & hash]) == null)
            //当该下标上没有值时,直接赋值新结点
            tab[i] = newNode(hash, key, value, null);
        //当下标上已经有值时,需要进行特殊处理.
        else {
            Node e; K k;
            //这里判断该数组下标的第一个数据的key是不是等于当前要插入的key,判断顺序=》先判断hash,然后key == p.key 和 key.equals(k)任意满足一个即可(重点),任意满足一个即可!! 一定要满足的条件是hash相同!!
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
             //这里可以理解为当结点是TreeNode时,向该树添加值
            else if (p instanceof TreeNode)
                e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
            else {
                //开启循环,如果binCount >= 7将会转为红黑树 binCount为table中对应Node的个数
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        //将结点添加到最后一个
                        p.next = newNode(hash, key, value, null);
                        //转为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //结点中找到了对应相等的key,停止循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //e不为null需要处理,即添加的数据已经存在,需要对其进行value覆盖
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    //将value赋值为新值
                    e.value = value;
                //hashMap没实现
                afterNodeAccess(e);
                //返回oldValue
                return oldValue;
            }
        }
        //结点数操作++
        ++modCount;
        //当添加后的size>threshold ,例如默认没没设置初始值,第一次扩容是size = 13
        if (++size > threshold)
            resize();
        //hashMap没实现
        afterNodeInsertion(evict);
        //因为没有oldValue,返回null
        return null;
    }

一些个人见解

  • key进行hash时,如果为null,hash为0,否则hash为key.hashCode() ^ (key.hashCode() >>> 16)值,使用>>>16的原因是要充分的利用高16位和低16位来得到hash,让key分布更均匀,因为key的落点分布是和map的size-1(size是2的倍数)相与,而size一般不会达到1<<16,所以直接用hash相与基本用不到高16位,在hash处理了过后可以充分利用高16位和低16位。(个人见解)

扩容

    final Node[] resize() {
        Node[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //新Map的size设置为old * 2, 如果size没有达到16则在if(newThr == 0)为newThr里面赋值 
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            //map中的数据为null, 但是设置了threshold值,就会将map的大小设置为threshold,初始化中如果设置了threshold则其存储的是map最开始应有的size
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            //如果初始化没设置threshold,或者扩容前的size没有16,扩容后大小为16
            newCap = DEFAULT_INITIAL_CAPACITY;
            //设置下一次需要扩容的size, 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //当map中没有数据,或者扩容后size < 16
        if (newThr == 0) {
            //设置下一次需要扩容的size
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node[] newTab = (Node[])new Node[newCap];
        table = newTab;
        //扩容开始
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node e;
                if ((e = oldTab[j]) != null) {
                    //置null, 方便gc回收
                    oldTab[j] = null;
                    //当该位置下只有一个结点,直接写入新数组
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    //树结点移植到新的table中,如果移植后的数结点小于等于6则将转化为链表
                    else if (e instanceof TreeNode)
                        ((TreeNode)e).split(this, newTab, j, oldCap);
                    //链表转移
                    else { // preserve order
                        Node loHead = null, loTail = null;
                        Node hiHead = null, hiTail = null;
                        Node next;
                        //循环,当下一个结点不为null
                        do {
                            next = e.next;
                            //这里在下面讲解
                            if ((e.hash & oldCap) == 0) {
                                //尾插法插入结点
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        //赋值
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
  • 通过扩容可以看到Map中存储Node用的数组的大小就是当前Map的容量,Node数组一定有下标的值为null

链表赋值算法讲解,因为map扩容是table.length<<1, table.length的值是2的幂次,确定node存储下标index是hash&oldTable.length - 1来进行确定的;
当oldTable的length为16时候,即下标的确定是 1111 & hash ,根据这个可以得出下标index为1的hash可能为:0001、1_0001、10_0001、11_0001、100_0001、101_0001、110_0001(加_是为了方便观看).......
在扩容后的length等于32,length-1 = 1_1111,此时下标index为1的hash为0001、10_0001、11_0001、100_0001、101_0001、110_0001....相比上面只少了1_0001,明显可以看出1_0001和上面其他数相比是因为'1'_0001,所以通过oldCap&e.hash == 0可以过滤掉类似'1'_0001这种数据,1_0001的新下标为1_0001,增加了1_0000。所以原下标的数组会分成两个链表,一个链表所在索引不动,另一个链表所在索引+oldTable的length。
通过oldCap&e.hash == 0将原本的链表拆成两根链表,然后分别赋值即可。
根据这个例子大致可以明白该思想。

注:map扩容采用的尾插法

remove

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

    /**
     * Implements Map.remove and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to match if matchValue, else ignored
     * @param matchValue if true only remove if value is equal
     * @param movable if false do not move other nodes while removing
     * @return the node, or null if none
     */
    final Node removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node[] tab; Node p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node 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 TreeNode)
                    node = ((TreeNode)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);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

remove

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

    /**
     * Implements Map.remove and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to match if matchValue, else ignored
     * @param matchValue if true only remove if value is equal
     * @param movable if false do not move other nodes while removing
     * @return the node, or null if none
     */
    final Node removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node[] tab; Node p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node node = null, e; K k; V v;
            //第一个就是要查找的值
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            //链表或者红黑树上查找对应的key
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode)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);
                }
            }
            //执行删除操作
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    //第一个结点就是查找到的结点
                    tab[index] = node.next;
                else
                    //p是node的前一个结点
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

迭代器

在HashMap中有一些的迭代器,通过这三个迭代器可以对key的集合,value的集合,key,value的集合进行遍历

    final class KeyIterator extends HashIterator
        implements Iterator {
        public final K next() { return nextNode().key; }
    }

    final class ValueIterator extends HashIterator
        implements Iterator {
        public final V next() { return nextNode().value; }
    }

    final class EntryIterator extends HashIterator
        implements Iterator> {
        public final Map.Entry next() { return nextNode(); }
    }

他们的父类

abstract class HashIterator {
        Node next;        // next entry to return
        Node current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node[] t = table;
            current = next = null;
            index = 0;
            //获取第一个结点
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Node nextNode() {
            Node[] t;
            Node e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            //获取下一个结点
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        public final void remove() {
            Node p = current;
            if (p == null)
                throw new IllegalStateException();
            //在这里不对应将会报错,因为迭代器遍历的时候会改变这两个数字,如果在使用迭代器的时候对容器进行了更改就会报错,所以迭代器使用期间不能进行增删处理.
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

针对HashMap从上面已经知道,只有结点的增删才会对modCount进行添加操作,当对原有结点进行修改时,不会进行modCount操作,所以在迭代器遍历时可以对结点进行修改操作,不能进行增删, 即迭代器遍历时不能进行size改变的操作。(List也同理)

        //这段代码会报Exception in thread "main" java.util.ConcurrentModificationException错误,删除掉else可以正常执行
        Map map = new HashMap<>();
        map.put(1, 9);
        map.put(2, 20);
        map.forEach((key, value) -> {
            if (key == 1) {
                map.put(1, 10);
            } else {
                map.put(9, 22);
            }
        });
        System.out.println(map.get(1));
        System.out.println(map.size());

总结

  • HashMap中key的第一判断条件为hash(null为0, 否则hash = kek.hashCode ^ (key.hashCode >>> 16)), hash相同判断第二条件,equals或者==满足一个即认定为相同key,put内容key满足以上条件时,只会更新value,不会更新key。

  • HashMap定位key所在table数组下标为table的长度size - 1 & key.hash。

  • HashMap添加内容时,先确定该数据在table表的存储下标index,当下标index处没有结点直接将该下标赋值给新添加数据的这个结点,如果有数据就尾插法添加到结点后面,当链表数据长度大于等于8(binCount >= TREEIFY_THRESHOLD - 1,binCount从0开始,TREEIFY_THRESHOLD等于8)就将该条链表转化为红黑树。

  • HashMap扩容:当size > threshold进行扩容(注:size为HashMap存储的元素大小,length为table表大小),每次扩容实际上是将hashMap的table表length*2,table表的length必须为2的n(区间1-30)次方。table*2后将原来数组下标的链表(红黑树)分为两个链表(红黑树,注:当红黑树大小<=6就会将红黑树转化为链表),此时会重新计算两条链表对应的下标index,一条链表为hoIndex,一条为低loIndex,两个index的关系为hoIndex = loIndex + size/2;

  • hashMap中有很多的迭代器,HashIterator 是他们共同的父类,hashMap中有一个modCount对HashMap的每次结点操作进行了记录,HashIterator中也有一个变量expectedModCount,在初始化迭代器的时候会将modCount赋值给expectedModCount,在进行遍历期间会对两个值进行比较,如果不想等级会抛出异常,所以在迭代器遍历期间不能对map进行添加和删除,因为添加删除modCount会+1,但是可以进行修改。

HashMap中还存在其他的迭代器这里没有进行贴出,本次源码差不多就到这里了。

你可能感兴趣的:(HashMap源码阅读)