Hashmap初始参数到底该怎么选

Hashmap初始参数到底该怎么选

从源码分析Hashmap初始化参数该怎么选

在new HashMap()的时候有些插件或是ide会提示给定初始化容量,但具体给多少,以前我都是预计装n个元素那就new HashMap(n); 现在还是决定看一下源码再做分析

// An highlighted block
//首先是单个参数
Map<Long,String> map = new HashMap<>(9);

public HashMap(int initialCapacity) {
      this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
   
 
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);//可以看到这个initialCapacity是通过tableSizeFor方法后赋值给了threshold(也就是扩容阈值,比如默认的16容量 * 默认loadfactor 0.75  = 12); 
        
        //看看tableSizeFor方法的实现
        //以我这个例子cap == 9为例,
        static final int tableSizeFor(int cap) {
        int n = cap - 1;//int n = 9 - 1 = 8;八位二进制 00001000
        n |= n >>> 1; //00001000 无符号右移一位-> 00000100 与 00001000 位或运算 得 00001100
        n |= n >>> 2; //00001100 无符号右移两位-> 00000011 与 00001100 位或运算 得 00001111
        n |= n >>> 4; //00001111 无符号右移四位-> 00000000 那再位或就不变了 所以最后值就是 00001111 也就是 15
        n |= n >>> 8; // 后面都是与00000000位或就不变了
        n |= n >>> 16;//
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;//这里再 + 1 最猴返回 16 
    }
    //这个方法的意思就是不停的右移再位或, 作用就是把低位的0化成1, 最后返回再加1。说白了就是得到大于cap的最小的2的次方数。
    //比如 9 10 11 12 13 那就都返回16 
    }
 

得出结论一:new HashMap<>(initialCapacity)只是初始化了threshold 而且对threshold 进行了处理,如果是非2的次方的数,会得到大于initialCapacity的最小的2的次方数。

那更具体的hashmap初始化在哪儿呢,稍微有了解过源码的应该知道,是在put方法里
看源码: 就在这里的resize方法里
Hashmap初始参数到底该怎么选_第1张图片
再看里面的具体实现,根据上面的初始化,多余的判断先注释

// An highlighted block
         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; //将上面初始化的阈值 threshold赋值给newCap
//            else {               // zero initial threshold signifies using defaults
//                newCap = DEFAULT_INITIAL_CAPACITY;
//                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
//            }
            if (newThr == 0) {
            //新的阈值为原来初始化的阈值 * loadFactor (本例来说就是16 * 0.75)
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                        (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;//将新的阈值赋值给threshold 
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//初始化一个newCap也就是最开始初始化的threshold也就是16长度的Node数组
            table = newTab;

得出结论二:hashmap里面table的正式初始化是在第一次put的时候的resize()方法里,初始化的长度为第一次初始化得到的threshold,新的threshold为第一次初始化得到的threshold乘以loadfactor

也就是说: 我new HashMap<>(9) ;在put一个元素后会生成一个 16长度,threshold为12的Hashmap。new HashMap<>(12) , new HashMap<>(14) 也一样。但是很明显,如果要装入的元素为12个或者14个,就肯定会有一次resize发生,会影响性能

最后结论:如果在 “能确定容量的前提下” 想用最少的容量又要避免resize,就应该手动设置loadfactor

举几个列子如下:

// An highlighted block
        Map<Long,String> map2 = new HashMap<>(3,3/4f);
        //等价于new HashMap<>(4,3/4f);
        
        Map<Long,String> map3 = new HashMap<>(4,4/4f);

        Map<Long,String> map4 = new HashMap<>(14,14/16f);
        //等价于new HashMap<>(16,14/16f);
        
        Map<Long,String> map5 = new HashMap<>(25,25/32f);
        //等价于new HashMap<>(32,25/32f);

Hashmap初始参数到底该怎么选_第2张图片

你可能感兴趣的:(java,hashmap,java,hashmap)