集合初始化时应指定初始值大小——学习《阿里Java开发手册》(一)

起源

  关于《阿里Java开发手册》之前有粗略的看过,作为一个刚出道的菜鸡,还以为仅仅是对于基于代码观赏性而来对其规范的定义,但是最近逛知乎,偶然的机会看到一篇非常skr的文章,推荐一下 [ 《阿里巴巴java手册》存疑:initialCapacity ],其作者从存疑到肯定的过程,让我受益匪浅,作为一个程序猿学习或者看到一个观点都应有这样一个态度。并且完全是颠覆了我对这个手册的映像,原来这个手册不仅是基于代码观赏性,还有更多的是,基于JVM性能方面做出开发时的一些规范性的建议。一个小点,就能引申出很多对于Java底层实现的思考。以后会一直学习这个手册,巩固自己的Java基础。

  本文是对于看了知乎文章 [ 《阿里巴巴java手册》存疑:initialCapacity ]这篇文章之后的个人的总结和理解。

开始

  我所转的这篇知乎,作者初始是看到了阿里手册上的这么一个推荐而引起的疑惑。如下图:
blogphoto.png
  以前刷面试题,看数据结构的时候,其实有对集合默认初始容量,及自动扩容机制有所了解。Java中各种集合本质其实也是数组+链表或者数组加二叉树的存储形式,不过对其进行了重新封装定义。回想看项目源码和写接口时,自己和他人写的代码,几乎没有指定集合初始值大小。到底这样会不会影响性能呢?根据知乎题主所提供的方法,我也对HashMap与ArrayList这两个集合类进行了性能测试。

  • HashMap 代码:
public class CapacityTest {
    public static void main(String[] args) {
        for (int num = 1; num < 1000000000; num = num * 10) {
            int capacity = (int) (num / 0.75 + 1);
            String test = "test";
            System.out.println("num is " + num);
            Map<Integer, String> map = new HashMap<>();
            Map<Integer, String> mapCapacity = new HashMap<>(capacity);
            long strat1 = System.nanoTime();
            for (int i = 0; i < num; i++) {
                map.put(i, test);
            }
            long end1 = System.nanoTime();
            System.out.println("没设置初始值的map: " + (end1 - strat1) + "  capacity: " + "null");

            long strat2 = System.nanoTime();
            for (int i = 0; i < num; i++) {
                mapCapacity.put(i, test);
            }
            long end2 = System.nanoTime();
            System.out.println("设置了初始值的map: " + (end2 - strat2) + "  capacity: " + capacity);
            System.out.println("*****************************************************************");
        }
    }
}

代码的运行结果:
blogphoto.png

从结果上可以看出,绝大多数数据还时有比较明显的性能上的提升,但是个别数据,如图中红色框标记的数据,单从耗时上来说甚至比没有设置的初始值还多,这是什么原因呢?

这里可以先简单的了解一下HashMap的底层实现,Java8中HashMap的构造比较复杂,由数组加(链表或红黑树)的结构来处理哈希碰撞,不得不说,这个设计模式真的是很优秀。Java8以前的HashMap的实现是数组+链表,元素储存的下标=key的hash值 % 数组长度 -1,其对应一个桶,里边会放很多元素,当大量元素都存放到一个桶中,这个桶就会有一个长长的链表,这个时候的HashMap就相当于一个单链表,失去它的优势,遍历元素花费很大。为此,Java8的HashMap引入了红黑树,会根据桶中的元素数量,来自动进行树形链形的切换,优化遍历元素时间复杂度高的问题。

当所需要数组长度超过阈值(capacity * loadFactor)时,HashMap调用resize()方法进行扩容,数组长度小于最大值时,扩容为原来两倍。由于数组不能自动扩容,扩容实际上是建立一个更长数组,替换原来的数组,并进行元素迁移。

由于,HashMap扩容机制的复杂性,所以出现红框里的现象也是正常的。当然,我们判断性能不能当从运行时间上来看,还有程序运行时所需要的空间,CPU的损耗等。

  • ArrayList 代码:
public class ArraryCapacityTest {
    public static void main(String[] args) {
        String test = "test";
        for (int num = 1; num < 100000000; num = num * 10) {
            int capacity = (int) (num + 1);
            System.out.println("num is " + num);
            List<String> list = new ArrayList<>();
            List<String> listCapacity = new ArrayList<>(capacity);
            long strat1 = System.nanoTime();
            for (int i = 0; i < num; i++) {
                list.add(test);
            }
            long end1 = System.nanoTime();
            System.out.println("没设置初始值的List: " + (end1 - strat1) + "  capacity: " + "null");

            long strat2 = System.nanoTime();
            for (int i = 0; i < num; i++) {
                listCapacity.add(test);
            }
            long end2 = System.nanoTime();
            System.out.println("设置了初始值的List: " + (end2 - strat2) + "  capacity: " + capacity);
            System.out.println("*****************************************************************");
        }
    }
}

代码运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8XPxGHB-1578240568289)(https://i.loli.net/2018/07/27/5b5ace7df2c58.png)]

同样的方法来测试ArrayList,结果体现的就比较明显,以为List组成结构比较简单,就是可动态扩容的数组,所以没有初始化大小通过扩容机制来动态扩容的ArrayList,在进行添加操作时会影响性能。

结束

  最后,实际编写代码时,我们可以估算一下所需要的容量,初始化集合的大小,如果不知道应该将集合初始化为默认值。

  现在个人菜鸡的阶段,写博客是为了巩固自己的基础,记录自己学到的用到的东西,做技术储备,将来用到的时候忘记了可以拿出来看看,是个人的总结。如果其中有什么地方写错了或者不严谨的话,欢迎大家可以指出。一起加油!

  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值