当前位置: 首页 > 科技观察

阿里巴巴Java开发手册建议在创建HashMap时设置初始容量,但设置多少合适呢?

时间:2023-03-20 23:51:29 科技观察

集合在Java开发的日常开发中经常会用到,而HashMap作为典型的K-V结构数据结构,对于Java开发者来说一定不陌生。关于HashMap,很多人对他都有一些基本的了解,比如他和hashtable的区别,他和concurrentHashMap的区别等等,这些都是比较常见的。关于HashMap的一些知识点和面试题我想大家肯定都比较熟悉,在开发中可以有效的应用。但是笔者在很多CodeReview和采访中发现,有一个关键的细节经常被忽略,那就是在创建HashMap的时候,是否应该指定容量?如果要指定,多少合适?为什么?设置HashMap在《HashMap中傻傻分不清楚的那些概念》中,我们得到了如下结论:HashMap有一个扩容机制,即当扩容条件满足时,它就会扩容。HashMap的扩容条件是当HashMap中的元素个数(size)超过阈值(threshold)时,会自动扩容。在HashMap中,threshold=loadFactor*capacity。因此,如果我们不设置初始容量,HashMap会随着元素数量的不断增加而多次扩容,而HashMap中的扩容机制决定了每次扩容时都需要重新构建哈希表,大大影响性能。因此,首先明确一点,我们建议开发者在创建HashMap时指定初始容量。而在《阿里巴巴开发手册》中也有这样的建议:HashMap的初始容量多少合适?既然建议我们在初始化集合的时候就指定初始值,那么我们在创建HashMap的时候,应该指定多少呢?有些人自然会想,我打算塞多少元素就塞多少。比如我打算塞7个元素,那就newHashMap(7)。但是,这样做不仅不对,而且上面方法创建的Map的容量也不是7。因为,当我们使用HashMap(intinitialCapacity)初始化容量时,HashMap并不会使用我们传入的initialCapacity直接作为初始容量。JDK默认会帮我们计算出一个相对合理的值作为初始容量。所谓合理值,其实就是找到大于用户传入值的2的1次方。也就是说,当我们用newHashMap(7)创建一个HashMap时,JDK会通过计算帮我们创建一个容量为8的Map;当我们用newHashMap(9)创建一个HashMap时,JDK会通过计算帮我们创建一个容量为16的Map。然而这个值看似合理,实际上并非如此。因为HashMap默认根据用户传入的capacity计算出的capacity并没有考虑loadFactor这个因子,只是简单的机械地计算出大约是这个数的2的1次方。loadFactor是负载因子。当HashMap中的元素个数(size)超过threshold=loadFactor*capacity时,会扩容。也就是说,如果我们将默认值设置为7,那么经过JDK处理后,HashMap的容量将设置为8。但是HashMap一旦元素个数达到8*0.75=6就会扩容,这显然是我们不想看到的。那么,设置什么值比较合理呢?这里可以参考JDK8中putAll方法的实现。guava(21.0版)也采用了这种实现方式。这个值的计算方法是:return(int)((float)expectedSize/0.75F+1.0F);比如我们打算把7个元素放入HashMap中,通过expectedSize/0.75F+1.0F计算,7/0.75+1=10,10经过JDK处理后会被设置为16,大大降低了机会的扩张。当HashMap内部维护的哈希表容量达到75%(默认)时,就会触发rehash,rehash过程比较耗时。因此,如果将初始化容量设置为expectedSize/0.75+1,可以有效减少冲突和错误。(大家结合这个公式就很好理解这句话了)因此,我们可以认为,当我们清楚知道HashMap的元素个数时,将默认容量设置为expectedSize/0.75F+1.0F是一个相对的表现。不错的选择,但同时也会牺牲一些内存。该算法在Guava中实现。开发时可以直接通过Maps类创建HashMap:Map的代码实现如下:(intexpectedSize){if(expectedSize<3){CollectPreconditions.checkNonnegative(expectedSize,"expectedSize");returnexpectedSize+1;}else{returnexpectedSize<1073741824?(int)((float)expectedSize/0.75F+1.0F):2147483647;}}不过,上面的操作是一种用内存换取性能的方式。实际使用时,要考虑内存的影响。但是,在大多数情况下,我们仍然认为内存是一种比较丰富的资源。但是话又说回来,有时候,我们真的需要设置HashMap的初始值吗,这个值应该设置多少,真的有这么大的影响吗?其实不一定!然而,性能大优化不只是优化细节一个一个堆砌吗?再烂,以后写代码的时候,用Maps.newHashMapWithExpectedSize(7);让同事和老板眼前一亮。或者哪天遇到面试官问你一些细节,你也可以有个印象,或者哪天你也可以把这个拿出来面试问问其他人~!哈哈哈哈。