HashMap源码分析

165 阅读4分钟

HashMap简介

HahsMap是用来存放键值对的一个容器,底层由数组+链表实现,Java1.8以后还引入了红黑树。其中,HashMap的key和value都可以取null值。

底层数据结构分析

HashMap类的属性

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

    private static final long serialVersionUID = 362498820763181265L;
    // 最大容量
    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;
    // 当桶的数量超过MIN_TREEIFY_CAPACITY时,向一个元素数达到TREEIFY_THRESHOLD的桶中插入节点时会将桶中的链表转化为红黑树实现,也就是变O(n)的查找转变为O(log n);
    static final int MIN_TREEIFY_CAPACITY = 64;
    // 存储链表的数组
    transient Node<K,V>[] table;
    // HashMap将数据转换成set的另一种存储形式,这个变量主要用于迭代功能。
    transient Set<Map.Entry<K,V>> entrySet;
    // 存入元素的个数
    transient int size;
    // 更改map结构的次数
    transient int modCount;
    // 扩容的临界值,threadhold = capacity * loadFactor
    int threshold;
    // 负载因子
    final float loadFactor;

put方法


    public V put(K key, V value) {
    // 主要实现在 putVal 方法内部
        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;
        // 如果 table 还未初始化或者table大小为0,则扩容,说明HashMap的数组是在第一次put元素的时候才分配空间的
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // (n - 1) & hash] 的作用就是对hash取模得到元素下标,等同于hash % n,如果这个桶内还没有链表,则创建新节点放入
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        // 如果桶中已经存在node链表
        else {
            Node<K,V> e; K k;
            // 比较桶中链表的第一个节点元素的hash值是否相等,key是否相等
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // 如果key不相等且为红黑树节点则放入树中
            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;
                    }
                    // 如果在链表中找到了key相同的节点则跳出循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 如果在桶中找到了key值完全相等的节点
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    // 用新的value更新旧的value
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        // 元素个数大于阈值则要扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

resize方法


    final Node<K,V>[] resize() {
        Node<K,V>[] 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;
            }
            // 正常扩容为原容量的2倍
            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
            newCap = oldThr;
        // 首次扩容,数组容量默认值为16,临界值为16*0.75=12
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //当只满足老阈值大于0的条件时,新阈值等于新容量*默认扩容因子
        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;
        // 如果旧的数组不为null,则要把旧数组中每个链表移动到新的数组中
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;//用于保存put后不移位的链表
                        Node<K,V> hiHead = null, hiTail = null;//用于保存put后移位的链表
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //如果与的结果为0,表示不移位,将桶中的头结点添加到lohead和lotail中,往后如果桶中还有不移位的结点,就向tail继续添加
                            if ((e.hash & oldCap) == 0) { 
                                if (loTail == null)//在后面遍历lohead和lotail保存到table中时,lohead用于保存头结点的位置,lotail用于判断是否到了末尾
                                    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;
    }