集合初始化时应指定初始值大小

开始

  我所转的这篇知乎,作者初始是看到了阿里手册上的这么一个推荐而引起的疑惑。如下图:

 

集合初始化时应指定初始值大小_第1张图片

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 map = new HashMap<>();
            Map 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("*****************************************************************");
        }
    }
}

代码的运行结果:

 

集合初始化时应指定初始值大小_第2张图片

 

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

这里可以先简单的了解一下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 list = new ArrayList<>();
            List 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("*****************************************************************");
        }
    }
}

代码运行结果:

集合初始化时应指定初始值大小_第3张图片

 

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

结束

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

 

你可能感兴趣的:(java基础)