前置知识
先来看看HashMap中的成员属性
解释:
size
当前的容器中Entry的数量,也就是当前K-V的数量loadFactory
装载因子,用来衡量HashMap满的程度,loadFactory的默认值是0.75threshold
临界值,当实际KV数量超过threshold时,就会触发扩容机制。
threshold = capatity * loadFactory
容量capatity
除了以上这些成员属性外,还有一个紧密关联的概念:capatity容量。
capatity与size不是同一个概念,size来表示当前的HashMap已经装了多少,capatity是容量,HashMap最多能装多少
利用反射获取HashMap的容量capatity的大小
看好这个方法,接下来要用
public static void printHashMapCapacity(HashMap map) throws Exception {
Class<? extends HashMap> mapClass = map.getClass();
Method capacity = mapClass.getDeclaredMethod("capacity");
capacity.setAccessible(true);
Integer invoke = (Integer) capacity.invoke(map);
System.out.println(invoke);
}
如果利用HashMap的无参构造,默认情况下的容量是16,当达到扩容机制时,就会扩容到32,以此类推
如果在new时,指定容量大小,则HashMap会选择第一个大于等于此数字的2的幂次作为初始容量
HashMap<Object, Object> hashMap = new HashMap<>();
printHashMapCapacity(hashMap);
// 默认的容量16
HashMap<Object, Object> map2 = new HashMap<>(1);
printHashMapCapacity(map2);
// 1 , 因为2的0次方是1
HashMap<Object, Object> map3 = new HashMap<>(7);
printHashMapCapacity(map3);
// 8 , 第一个大于7的2的次幂是8
阿里Java开发手册中规定,在初始化HashMap时,应该尽量指定其初始大小
loadFactory和threshold
这两个属性用来指定HashMap的扩容机制
threshold = loadFactory * capacity
当HashMap中的size超过threshold时,就会触发扩容,每次扩容后的容量是原始容量的2倍
从默认的初始容量16扩容到32、64、128…
loadFactory是装载因子,用来衡量HashMap满的程度,默认值是0.75f。
设置成0.75的好处是:capacity是2的次幂,两个数的乘积还是整数,避免浮点数造成影响。
我们初始化HashMap时,除了可以指定初始化容量大小外,还可以指定装载因子的大小的,但是修改装载因子的值是不推荐的。
为什么要设置初始化容量
当HashMap 发生扩容时,会新建一个指定容量的桶,然后将原来的Entry拷贝到新的桶中。
此过程会有较大的资源消耗。
为了避免在向HashMap中put过多元素时频繁发生扩容,需要指定一个合适大小的初始化容量。
初始化容量设置多少合适
我们的目的是避免频繁发生扩容,所以一次性指定初始化容量合适大小。
并不是我要塞入多少个元素,就初始化为多少,来看一个例子:
当我要向HashMap中put 7个元素,我将HashMap初始化为7
HashMap map = new HashMap(7);
因为传入的是一个7,HashMap会选择一个大于等于该值的2的次幂作为容量capacity
根据loadFactory=0.75,计算出threshold = 8 * 0.75 = 6
当我们put第7个元素时,就会触发扩容,扩容到16。
但是我们就想put 7个元素,在第7个时触发扩容,导致了性能和空间的浪费。
所以,我们在初始化时,选择多少的初始化容量,值得考虑。
借鉴HashMap 中putAll()方法,得出以下公式:
initCapatity = expectedSize / 0.75F + 1.0F
这个计算方法虽然浪费了一些空间,但是在性能上却提升了。
当我们要put 7个元素, initCapacity = 7 / 0.75 + 1 = 10 ,经过HashMap的计算,第一个大于10的2的次幂的数是16,所以HashMap的容量是16。
16 * 0.75 = 12 ,当我要put第7个元素时,并不会发生扩容,性能得到提升。
而且与第一次我们设置初始化容量为7相比,最终所占用的空间是一样的。
为什么选择2的次幂作为容量
因为,在put时,HashMap会根据key的hash来计算对应Entry所在桶的位置。
通常都是利用求余来实现的
index = hash % capacity
但是直接利用%来计算是非常慢的,效率非常第低。
我们知道,在计算机中,最快的运算就是位运算了。
存在这样一个规律:当capacity是2的次幂时,满足以下恒等式
hash % capacity == hash & (capacity - 1)
在我们创建HashMap时,就做了第一步处理,选择大于等于给定数字的第一个2的次幂作为容量,就保证了。