HashMap源码解读

HashMap数据结构

在jdk1.7中,HashMap采用数组+链表(拉链法)。因为数组是一组连续的内存空间,易查询,不易增删,而链表是不连续的内存空间,通过节点相互连接,易删除,不易查询。HashMap结合这两者的优秀之处来提高效率。

而在jdk1.8时,为了解决当hash碰撞过于频繁,而链表的查询效率(时间复杂度为O(n))过低时,当链表的长度达到一定值(默认是8)时,将链表转换成红黑树(时间复杂度为O(logn)),极大的提高了查询效率。

本文使用JDK8的HashMap源码进行解析

源码解读

DEFAULT_INITIAL_CAPACITY:HashMap的默认大小是16

 MAXIMUM_CAPACITY:最大容量是2^30,如果指定的容量大于2^30,则使用2^30为最大容量

DEFAULT_LOAD_FACTOR:默认的负载因子为0.75

/**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

链表中的Node节点

Node节点是以内部类的方式实现的,将hash、key、value、next属性都封装在Node节点上

 /**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     */
    static class Node<K,V> implements Map.Entry<K,V> {
        //key的Hash值
        final int hash;
        //key值
        final K key;
        //key对应的value值
        V value;
        //下一个Node的内存地址
        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; }

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

put方法


    public V put(K key, V value) {
        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;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                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;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

put方法中可以看到hash(key)这个方法将key进行了hash,了解下其中的hash实现


    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

Hash算法实现中有几个细节问题:

为什么要无符号右移16位做异或运算?

将h右移16位相当于把h的高区16位右移至低区16位,然后同原hashCode做异或运算,可以将高低位的二进制特征混合起来。

从上文可知高区的16位与原hashcode相比没有发生变化,低区的16位发生了变化

我们可知通过上面(h = key.hashCode()) ^ (h >>> 16)进行运算可以把高区与低区的二进制特征混合到低区,那么为什么要这么做呢?

我们都知道重新计算出的新哈希值在后面将会参与hashmap中数组槽位的计算,计算公式:(n - 1) & hash,假如这时数组槽位有16个,则槽位计算如下:

仔细观察上文不难发现,高区的16位很有可能会被数组槽位数的二进制码锁屏蔽(实际上是只有四位参与了运算),如果我们不做刚才移位异或运算,那么在计算槽位时将丢失高区特征

也许你可能会说,即使丢失了高区特征不同hashcode也可以计算出不同的槽位来,但是细想当两个哈希码很接近时,那么这高区的一点点差异就可能导致一次哈希碰撞,所以这也是将性能做到极致的一种体现。

由于和(length-1)运算,length 绝大多数情况小于2的16次方。所以始终是hashcode 的低16位(甚至更低)参与运算。要是高16位也参与运算,会让得到的下标更加散列。

所以这样高16位是用不到的,如何让高16也参与运算呢。所以才有hash(Object key)方法。让他的hashCode()和自己的高16位^运算。所以(h >>> 16)得到他的高16位与hashCode()进行^运算。

为什么要使用异或运算?

异或运算能更好的保留各部分的特征,如果采用&运算计算出来的值会向1靠拢,采用|运算计算出来的值会向0靠拢。

 

了解完Hash实现如何降低Hash碰撞后,这时候就需要我们来看一下其中的putVal()方法

1.判断当前数组table是否为空,如果为null或者数组长度为0,则调用resize()方法对数组进行初始化

2.此时通过i = (n -1) & hash来计算需要存放的数组索引,如果当前数组table[i]为null则将当前的键值对放在当前桶位置

3.如果当前桶有值( Hash 冲突),那么就要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等,相等就赋值给 e

4.如果当前桶为红黑树,那么需要按照红黑树的方式写入数据

5.如果是链表,就需要将当前的key,value封装成新的Node写入当前桶的链表后边

6.接着判断当前桶链表的大小是否大于预设的阈值,大于时需要转换为红黑树的结构

7.如果在遍历当中找到了相同的key直接退出遍历

8.如果 e != null 就相当于存在相同的 key,那就需要将值覆盖。

9.最后判断是否需要扩容

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)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                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;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

 

 

 

 


版权声明:本文为weixin_43718648原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。