HashMap精读

68 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

相信很多同学都在实际开发中经常使用到HashMap,这个是个key-value的容器。在很多应用场景都有大量使用,比如从数据库中获取到大量数据,然后在后续代码中又需要重复使用到这批数据,就可以以数据主键为key,对象为value组装成HashMap,方便后续使用。 知其然而要知其所以然,所以接下来我们就一起来看看HashMap源码都有哪些魔法吧~

1. HashMap中的属性

//默认容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量2^30
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;
//扩容临界值
int threshold;
//负载因子
final float loadFactor;

threshold

扩容的临界值,threshold=initialCapacity*loadFactor,所以在设置HashMap的initialCapacity时,应该根据具体需要装入HashMap的数据量来反推最佳initialCapacity,避免HashMap的扩容操作。

loadFactor

默认值为0.75,这是综合时间和空间开销的最优值,如果大于这个值虽然会减小空间开销,但是会增长操作时间,可能影响的操作包括get和put。

2、扩容方法

扩容的核心方法是HashMap中的resize方法

  1. 获取HashMap中的数组长度
  2. 如果数组内有值: 判断数组长度是否大于最大容量(MAXIMUM_CAPACITY)值,如果大于则直接将threshold赋值为Integer.MAX_VALUE,直接返回方法;如果数组长度左移一位(二进制表达向左移一位,空的填0)小于MAXIMUM_CAPACITY且数组长度不小于初始容量,则将原始的容量无符号左移一位; 如果数组内没值: 判断是否初始化了threshold,如果没初始化,则将初始容量设置为默认容量大小(DEFAULT_INITIAL_CAPACITY),将threshold设置为DEFAULT_LOAD_FACTOR*DEFAULT_INITIAL_CAPACITY
  3. 如果新的threshold未初始化,根据新设置的容量,生成新的threshold
  4. 根据新的容量值创建数组,如果HashMap中原始的数组存在,则开始进行数据结构的转变。遍历原始数组,获取节点Node,如果当前位置存储对象的next为空,则说明它是单节点,不是树状或者链表结构,直接根据【hash&(length-1)】获取到角标进行设置。如果当前存储对象是treenode,则用split函数处理这颗红黑树。除此之外就是链表结构,当前节点如果hash&oldCap==0,则位置不变,反之角标变成【原索引+oldCap】
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;
        }
        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;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        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) {
                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;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    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;
}