Java中使用频率最高的列表一定是ArrayListLinkedList,它们分别使用了不同的数据结构,前者是数组,后者是链表

如果学习过数据结构,我相信数组和链表的时间复杂度一定耳熟能详:

  • 数组,随机访问时间复杂度是 O(1),插入是 O(n)
  • 链表,随机访问时间复杂度是 O(n),插入是 O(1)

那么在 Java 中,应对插入多,访问少的场景,是否就应该使用LinkedList呢?不如写一段代码测试下两者随机访问和插入的性能吧。

代码比较简单,就是对大小为 10 万的LinkedListArrayList进行随机访问和增加元素到随机位置,并使用 StopWatch来测量执行需要的时间:


    /**
     * LinkedList随机访问
     */
    private void linkedListGet() {
        // 生成拥有100000个元素的 LinkedList
        List<Integer> list = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toCollection(LinkedList::new));
        // 从 (1,100000) 取一个随机数作为下标,访问list中的这个元素,并多次循环
        IntStream.rangeClosed(1, 100000).forEach(i -> list.get(ThreadLocalRandom.current().nextInt(100000)));
    }

    /**
     * ArrayList随机访问
     */
    private void arrayListGet() {
        List<Integer> list = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toCollection(ArrayList::new));
        IntStream.rangeClosed(1, 100000).forEach(i -> list.get(ThreadLocalRandom.current().nextInt(100000)));
    }

    /**
     * LinkedList随机插入
     */
    private void linkedListAdd() {
        List<Integer> list = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toCollection(LinkedList::new));
        IntStream.rangeClosed(1, 100000).forEach(i -> list.add(ThreadLocalRandom.current().nextInt(100000), 1));
    }

    /**
     * ArrayList随机插入
     */
    private void arrayListAdd() {
        List<Integer> list = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toCollection(ArrayList::new));
        IntStream.rangeClosed(1, 100000).forEach(i -> list.add(ThreadLocalRandom.current().nextInt(100000), 1));
    }


    @Test
    public void test03() {
        StopWatch stopWatch = new StopWatch();

        stopWatch.start("linkedListGet");
        linkedListGet();
        stopWatch.stop();

        stopWatch.start("arrayListGet");
        arrayListGet();
        stopWatch.stop();

        log.info("随机访问速度对比:{}", stopWatch.prettyPrint());


        StopWatch stopWatch2 = new StopWatch();

        stopWatch2.start("linkedListAdd");
        linkedListAdd();
        stopWatch2.stop();

        stopWatch2.start("arrayListAdd");
        arrayListAdd();
        stopWatch2.stop();

        log.info("随机插入速度对比:{}", stopWatch2.prettyPrint());
    }

结果非常 amazing:

15:33:28.311 [main] INFO com.example.util.StopWatchTest - 随机访问速度对比:StopWatch '': running time (millis) = 5177
-----------------------------------------
ms     %     Task name
-----------------------------------------
05157  100%  linkedListGet
00020  000%  arrayListGet

15:33:54.868 [main] INFO com.example.util.StopWatchTest - 随机插入速度对比:StopWatch '': running time (millis) = 26554
-----------------------------------------
ms     %     Task name
-----------------------------------------
25326  095%  linkedListAdd
01228  005%  arrayListAdd

随机访问和预期一样,使用数组作为底层数据结构的 ArrayList 拥有压倒性的优势,耗时只有 20 毫秒。

但是随机插入也是 ArrayList 占优,它只要 1.28 秒,LinkedList 居然需要 25.3 秒。

不是说好的链表插入更快吗?这咋回事?

翻了 LinkedList 源码可以发现,插入时间复杂度是 O(1) 的前提是,已经有了那个要插入节点的指针。

但是怎么才能获得那个节点的Node?还不是得循环嘛,循环也是需要时间的。

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

既然都到这儿了,我们不妨再测试下顺序插入,LinkedList 能否扳回一城?

    @Test
    public void testWrite() {
        StopWatch stopWatch = new StopWatch();

        List<Integer> arrayList = new ArrayList<>();
        stopWatch.start("ArrayList");
        IntStream.rangeClosed(1, 10000000).forEach(arrayList::add);
        stopWatch.stop();

        List<Integer> linkedList = new LinkedList<>();
        stopWatch.start("LinkedList");
        IntStream.rangeClosed(1, 10000000).forEach(linkedList::add);
        stopWatch.stop();

        log.info(stopWatch.prettyPrint());
    }

可惜啊,ArrayList 依然遥遥领先:

16:00:27.385 [main] INFO com.example.util.StopWatchTest - StopWatch '': running time (millis) = 3334
-----------------------------------------
ms     %     Task name
-----------------------------------------
00687  021%  ArrayList
02647  079%  LinkedList

这回悬着的心终于死了,无论什么场景,ArrayList 都完胜 LinkedList。

毕竟 LinkedList 的亲爹 Josh Bloch 都发话了:正经人谁用 LinkedList?

本文由mdnice多平台发布


Jerry_ω_̥
1 声望0 粉丝