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方法里
再看里面的具体实现,根据上面的初始化,多余的判断先注释
// 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);