当HashMap的size达到临界值capacity * loadFactor - 1时,HashMap会进行扩容,将自身容量增加一倍。
比如对未指定capacity和loadFactor的HashMap,缺省容量和负载因子分别为16和0.75,因此当map中存储的元素数量达到16 * 0.75 - 1即为11时,该map会将自身容量扩大到2 * 16 = 32。
在某些情况下,扩容耗费较多时间。如下面的代码
public static void main()
{
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < 1200000; i += 3)
{
map.put(i, i + 1);
}
}
JProfiler的统计信息如下,可以看到扩容的耗费在transfer方法上。
transfer功能是创建一个新的table并将map中现有的元素依次拷贝到新table中。上例中map中最终元素数为40万,扩容了16次,每次扩容需要transfer的数据数目分别是16 * 0.75 - 1 = 11, 32 * 0.75 - 1 = 23, 64 * 0.75 - 1 = 47, 128 * 0.75 - 1 = 71, ... ... , 262016 * 0.75 - 1 = 196511, 524032 * 0.75 - 1 = 393023
为了在数据量较多时避免扩容,可以在创建HashMap时指定capacity。capacity的计算示意如下
如果map的最终元素数量落在如下的左面区间,那么HashMap不会扩容,否则会扩容。
HashMap的构造函数自动会根据提供的initialCapacity寻找大于等于initialCapacity的并且是2的指数幂的最小数值。
new HashMap(17)
或
new HashMap(31)
创建的HashMap的容量都是32,但我们知道capacity为32的map扩容临界值为32 * 0.75 - 1 =
23
,因此若最终map会放入31个元素,map会在放入第
23
个元素的时候将容量从32扩大到64。
创建HashMap时指定的capacity有两种计算方式,假定map容纳的元素数最终是数值m
那么
方式一
new HashMap(m * 4 / 3 + 3)
创建的map不会扩容
原因是对临界值m = 2^n * 0.75 - 1,m * 4 / 3 + 3结果2^n + 5/3取整为2^n + 1,而HashMap构造器的参数为2^n + 1时其capacity会是2^(n + 1),举例如下
22个元素对应的值22 * 4 / 3 + 3取整结果为32;23
个元素对应的值22 * 4 / 3 + 3取整结果为33,new HashMap(33)创建的map最终容量是64.
方式二
是直接找出大于m * 4 / 3 + 3的并且是2的指数幂的最小数值,比如m为17时,找出 17 * 4 / 3 + 3 = 25并且是2的指数幂的值为32,示例函数如下
int getMinCapacityNotResize(int targetEntrySize)
{
if (targetEntrySize <= 0)
{
return 0;
}
double exponent = Math.log(targetEntrySize) / Math.log(2);
double defaultInitCapacity = Math.pow(2, Math.ceil(exponent));
double capacity = defaultInitCapacity;
if (targetEntrySize >= defaultInitCapacity * 3 / 4 - 1)
{
capacity = defaultInitCapacity * 2;
}
return (int) capacity;
}