java基础之HashMap中的capacity和loadFactor详解

上篇HashMap博客已经对存储结构以及存放过程进行了简单分析。今天我们来对HashMap中的容量(capacity)以及加载因子(loadFactor)分析一下这两个东西对于map的作用。看这篇博客之前,我已经认为你了解了hashmap的存储结构了。

我在开发中写的最多的HashMap声明为:Map map = new HashMap();不知道大家是不是这样的。如果你是下面这两种:

// 第一种
Map map = new HashMap(int initialCapacity);

// 第二种
Map map = new HashMap(int initialCapacity, float loadFactor);

 我相信你对容量和加载因子是理解的(暂且不说用的好与不好,至少我觉得你应该是知道这两个东西的作用)。

说到底我们的HashMap其实就是一个容器,里面放一些key-value而已,map是一个动态的容器(也就是说容量是不固定的),既然容量不固定是多少?是无限大的吗?不知道大家有没有想过这样的问题。这就涉及到了加载因子和容量两个东西。

我们直接上源码看吧。既然map有容量一说,就说明还是有大小之分的,那么我们的map的大小是多少,我们去看下源码中怎么说的。我们正常使用map的步骤为:

// 初始化
Map map = new HashMap();
// put值
map.put("key", "value");

 一:map初始化

/**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
   
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    // 以上是默认初始化操作,可以看到只是初始化了一个loadFactor(加载因子),
    // 其他的什么都没做,我们暂时先搁置这个东西,记着初始化里面没有太多操作就行了

二: map.put才是重点,我们去看下put方法 

/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */

    // 真正的put方法(重点中的重点)
    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,说明这个方法结束了,下面的代码不会走了
                return oldValue;
            }
            // 以上这个if,是找到了与key值相同的对象,因此覆盖了旧的value值
            // 以上代码不解释具体原理了,可以参考我的上一篇博客
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

下面我们把map扩容的代码粘出来,具体说一下:

        // 如果size > threshold 其实这是第二次resize了
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;

        // 这段代码是map中没有找到与新key相同的对象,执行的逻辑
        // 我们来看下:

        // ++modCount; 
        // 这行是说明此map被执行的次数

        // if (++size > threshold)  {
        //     resize();
        // }
        // 这三行我们的容量和加载因子就派上用场了
        // 那么,此时size是多少?threshold又是什么?初始化的时候并没有这个东西呀,
        // 这两个东西都是在一个resize()方法中进行的操作,我们来看下resize()方法
        // resize()方法我们分成两大部分来看
        // 其一:控制容量(我们暂且分析这一个)
        // 我们put方法最上面先resize过一次了(自行找执行过程),我们来看看,我们的最初容量是多少
        // threshold = newThr;这行就是赋值的部分
        // 我们看这行上面有好几个if else,这就是判断threshold该赋什么样的值
        // 当我们第一次执行这个方法是,我们oldCap(我们map中table数组的长度,此时table为
        // null,oldCap即为0)
        // oldCap为0执行下面两行
        // newCap = DEFAULT_INITIAL_CAPACITY; 默认容量为16
        // newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        // newThr 下次resize的极限值为:默认容量 * 默认加载因子 = 16 * 0.75 = 12
        // 也就是说如果map的size达到12时,就会重新resize(扩容)
        
        // 以上就是第一次初始化的时候map的分配情况,
        // 那么在往下扩容的话,则,容量 和 极限值都是成倍正常,16 32 64......


        // 其二:构建新的map容器
    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;

        // 以上是其一:控制容量(我们暂且分析这一个)部分

        // 以下是其二:构建新的map容器,链表赋值给新的数组而已
        @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;
    }

因此,大家看到了吧,map动态的原理了吧,其实他也有容量一说的。

那么简单总结一下map的特点和原理:

1、map首先为数组配合链表的数据结构,数组默认长度为16(也就是默认容量)

2、map进行put的时候,如果有相同key,则新value替换旧value,返回旧value;否则,插入链表,返回null,size++

3、map的扩容,第一次是默认的容量(16),默认的扩容极限值(16 * 0.75 = 12);如果size达到12时,进行下次扩容,容量和极限值均扩容成原来的两倍,32,24;64,48.....,当然也有极限值,容量最大值为MAXiMUM_CAPACITY = 1 <<30,扩容临界值threshold = Integer.MAX_VALUE

最后再抛个问题:网上大家都在说为什么map的默认加载因子为0.75?

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值