java 中 Hashmap 容量的初始化

本文探讨了Java中HashMap初始化容量的重要性。HashMap在默认情况下容量为16,但可以通过构造函数指定容量,选择大于该数字的最小2的幂。初始化容量合理设置可以避免频繁扩容,提高性能。扩容阈值为容量的75%,当达到该比例时HashMap会自动扩容,这个过程消耗资源。理想的初始化容量计算公式为(expectedSize / 0.75F) + 1,以减少冲突和误差。设置初始容量是平衡性能和内存使用的关键,需要根据实际应用需求进行选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HashMap使用HashMap(int initialCapacity)对集合进行初始化。

在默认的情况下,HashMap的容量是16。但是如果用户通过构造函数指定了一个数字作为容量,那么Hash会选择大于该数字的第一个2的幂作为容量。比如如果指定了3,则容量是4;如果指定了7,则容量是8;如果指定了9,则容量是16。
现在来测试一下:

//测试容量
	//设置初始化容量为9,结果自动计算 为16   。
	Map<String , String> map3 =  new HashMap<String, String>(9); 
	map3.put("A", "a");
	Class<?> mapType = map3.getClass();
	Method capacity = mapType.getDeclaredMethod("capacity");
	capacity.setAccessible(true);
	System.out.println("实际容量:"+capacity.invoke(map3));   //16

当我们通过HashMap(int initialCapacity)设置初始容量的时候,HashMap并不一定会直接采用我们传入的数值,而是经过计算,得到一个新值,目的是提高hash的效率。比如1->1、3->4、7->8和9->16。

未初始化容量

int  a= 10000000;   //设置一个扩容的上限
			Map<Integer,Integer> map = new HashMap<Integer, Integer>();
			long s1 = System.currentTimeMillis(); // 获取一个时间节点
			System.out.println(s1);
			for(int i =0 ;i<a;i++) {
				map.put(i, i);
			}
			long s2 = System.currentTimeMillis(); //获取第二个时间节点
			System.out.println(s2);
			System.out.println("消耗时间"+(s2-s1));    //  9430

初始化容量为5000000

Map<Integer,Integer> map1 = new HashMap<Integer, Integer>(a/2);
			long s3 = System.currentTimeMillis();
			for(int i=0;i<a;i++) {
				map1.put(i, i);
			}
			long s4 = System.currentTimeMillis();
			System.out.println("消耗时间:"+(s4-s3));     //6129

初始化容量为10000000

Map<Integer,Integer> map2 = new HashMap<Integer, Integer>(a);
			long s5 =System.currentTimeMillis();
			for(int i=0;i<a;i++) {
				map2.put(i, i);
			}
			long s6 =System.currentTimeMillis();
			System.out.println("消耗时间:"+(s6-s5));      //4470

以上的代码,我们创建了3个HashMap,分别使用默认的容量(16)、使用元素个数的一半(5百万)作为初始容量和使用元素个数(一千万)作为初始容量进行初始化,然后分别向其中put 一千万个KV。

从上面的打印结果中可以得到一个初步的结论:在已知HashMap中将要存放的KV个数的时候,设置一个合理的初始化容量可以有效地提高性能。下面我们来简单分析一下原因。

我们知道,HashMap是有扩容机制的。所谓的扩容机制,指的是当达到扩容条件的时候,HashMap就会自动进行扩容。而HashMap的扩容条件就是当HashMap中的元素个数(Size)超过临界值(Threshold)的情况下就会自动扩容。

threshold = loadFactor * capacity
在元素个数超过临界值的情况下,随着元素的不断增加,HashMap就会发生扩容,而HashMap中的扩容机制决定了每次扩容都需要重建hash表,这一操作需要消耗大量资源,是非常影响性能的。因此,如果我们没有设置初始的容量大小,HashMap就可能会不断发生扩容,也就使得程序的性能降低了。

另外,在上面的代码中我们会发现,同样是设置了初始化容量,设置的数值不同也会影响性能,那么当我们已知HashMap中即将存放的KV个数的时候,容量的设置就成了一个问题。

initialCapacity = expectedSize / 0.75F + 1.0F
initialCapacity = (需要存储的元素个数 / 负载因子) + 1
**-**这个算法实际上是JDK8中putAll()方法的实现。这是公式的得出是因为,当HashMap内部维护的哈希表的容量达到75%时(默认情况下),就会触发rehash(重建hash表)操作。而rehash的过程是比较耗费时间的。所以初始化容量要设置成expectedSize/0.75 + 1的话,可以有效地减少冲突,也可以减小误差。

总结
当我们想要在代码中创建一个HashMap的时候,如果我们已知这个Map中即将存放的元素个数,给HashMap设置初始容量可以在一定程度上提升效率。

但是,JDK并不会直接拿用户传进来的数字当做默认容量,而是会进行一番运算,最终得到一个2的幂。而为了最大程度地避免扩容带来的性能消耗,通常是把默认容量的数字设置成expectedSize / 0.75F + 1.0F。

这种算法实际上是一种使用内存换取性能的做法,在真正的应用场景中要考虑到内存的影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值