When using the Java project in the collection, you should avoid some of the pit!

No public replies back, " learning ", authors get exclusive boutique secret information

640?wx_fmt=png

640?wx_fmt=jpeg

Scan QR code below posters, audition courses:

(Detailed course outline, please see the end of the text)

640?wx_fmt=jpeg

640?wx_fmt=png

This article Source: crossoverJie

Foreword

Not long ago, to help colleagues with reviewa jobfound a lot of friends in the line and realize the function code is still not enough attention to detail required when performing problem of slow, so there was this article.

ArrayList stepped pit

 
  

First we look at this code thing what is the problem?

In fact, in most cases, this problem is nothing, nothing more than to cycle ArrayListwrite data only.

However, in exceptional circumstances, such as where the return is enormous follow-up data will be a problem.getData()temp.add(str)

For example, we reviewfound that sometimes this data will be returned when the code up to 2000W, then the ArrayListquestion written on the highlights out.

Filled pit Guide

We all know that ArrayList is implemented by the array, and the limited length of the data; the need for expansion of the array at the right time.

Here an example to be inserted into a tail add (E e).

640?wx_fmt=jpeg

When we initialize a length of 2 ArrayList, the three data write and Wang Libian ArrayListhave to expansion, that is, to copy the data before a new array to an array of length 3.

640?wx_fmt=jpeg

The reason is 3, because the new length of the original length = 1.5 *

By source we can see that ArrayListthe default length is 10.

640?wx_fmt=jpeg640?wx_fmt=jpeg

But in fact we not created during initialization of the array.DEFAULT_CAPACITY=10

640?wx_fmt=jpeg

But Wang Libian addfirst time data will be expanded to 10.

既然知道了默认的长度为 10 ,那说明后续一旦写入到第九个元素的时候就会扩容为 10*1.5=15。这一步为数组复制,也就是要重新开辟一块新的内存空间存放这 15 个数组。

一旦我们频繁且数量巨大的进行写入时就会导致许多的数组复制,这个效率是极低的。

但如果我们提前预知了可能会写入多少条数据时就可以提前避免这个问题。

比如我们往里边写入 1000W 条数据,在初始化的时候就给定数组长度与用默认 10 的长度之间性能是差距巨大的。

我用 JMH 基准测试验证如下:

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)	
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)	
public class CollectionsTest {	
    private static final int TEN_MILLION = 10000000;	
    @Benchmark	
    @BenchmarkMode(Mode.AverageTime)	
    @OutputTimeUnit(TimeUnit.MICROSECONDS)	
    public void arrayList() {	
        List<String> array = new ArrayList<>();	
        for (int i = 0; i < TEN_MILLION; i++) {	
            array.add("123");	
        }	
    }	
    @Benchmark	
    @BenchmarkMode(Mode.AverageTime)	
    @OutputTimeUnit(TimeUnit.MICROSECONDS)	
    public void arrayListSize() {	
        List<String> array = new ArrayList<>(TEN_MILLION);	
        for (int i = 0; i < TEN_MILLION; i++) {	
            array.add("123");	
        }	
    }	
    public static void main(String[] args) throws RunnerException {	
        Options opt = new OptionsBuilder()	
                .include(CollectionsTest.class.getSimpleName())	
                .forks(1)	
                .build();	
        new Runner(opt).run();	
    }	
}

640?wx_fmt=jpeg

根据结果可以看出预设长度的效率会比用默认的效率高上很多(这里的 Score 指执行完函数所消耗的时间)。

所以这里强烈建议大家:在有大量数据写入 ArrayList 时,一定要初始化指定长度。


再一个是一定要慎用 add(intindex,E element) 向指定位置写入数据。

640?wx_fmt=jpeg

通过源码我们可以看出,每一次写入都会将 index 后的数据往后移动一遍,其实本质也是要复制数组;

但区别于往常规的往数组尾部写入数据,它每次都会进行数组复制,效率极低。

LinkedList

提到 ArrayList 就不得不聊下 LinkedList 这个孪生兄弟;虽说都是 List 的容器,但本质实现却完全不同。

640?wx_fmt=jpeg

LinkedList 是由链表组成,每个节点又有头尾两个节点分别引用了前后两个节点;因此它也是一个双向链表。

所以理论上来说它的写入非常高效,将不会有 ArrayList 中效率极低的数组复制,每次只需要移动指针即可。

这里偷懒就不画图了,大家自行脑补下。

对比测试

坊间一直流传:

LinkedList 的写入效率高于 ArrayList,所以在写大于读的时候非常适用于 LinkedList 。

    @Benchmark	
    @BenchmarkMode(Mode.AverageTime)	
    @OutputTimeUnit(TimeUnit.MICROSECONDS)	
    public void linkedList() {	
        List<String> array = new LinkedList<>();	
        for (int i = 0; i < TEN_MILLION; i++) {	
            array.add("123");	
        }	
    }

640?wx_fmt=jpeg

这里测试看下结论是否符合;同样的也是对 LinkedList 写入 1000W 次数据,通过结果来看初始化数组长度的 ArrayList 效率明显是要高于 LinkedList

但这里的前提是要提前预设 ArrayList 的数组长度,避免数组扩容,这样 ArrayList 的写入效率是非常高的,而 LinkedList 的虽然不需要复制内存,但却需要创建对象,变换指针等操作。

而查询就不用多说了, ArrayList 可以支持下标随机访问,效率非常高。

LinkedList 由于底层不是数组,不支持通过下标访问,而是需要根据查询 index 所在的位置来判断是从头还是从尾进行遍历。

640?wx_fmt=jpeg

但不管是哪种都得需要移动指针来一个个遍历,特别是 index 靠近中间位置时将会非常慢。

总结

高性能应用都是从小细节一点点堆砌起来的,就如这里提到的 ArrayList 的坑一样,日常使用没啥大问题,一旦数据量起来所有的小问题都会成为大问题。

所以再总结下:

  • 再使用 ArrayList 时如果能提前预测到数据量大小,比较大时一定要指定其长度。

  • 尽可能避免使用 add(index,e) api,会导致复制数组,降低效率。

  • 再额外提一点,我们常用的另一个 Map 容器 HashMap 也是推荐要初始化长度从而避免扩容。

本文所有测试代码:

https://github.com/crossoverJie/JCSprout/blob/master/src/main/java/com/crossoverjie/basic/CollectionsTest.java

END

如有收获,请划至底部,点击“在看”,谢谢!

640?wx_fmt=png

Welcome to long press drawing public attention No. huperzine architecture notes

640?wx_fmt=jpeg

BAT architecture experience purse

Guess you like

Origin blog.csdn.net/qq_42046105/article/details/100190472