美文网首页Java
你不知道的LinkedList(二):LinkedList的增删

你不知道的LinkedList(二):LinkedList的增删

作者: 冬天里的懒喵 | 来源:发表于2020-08-17 11:30 被阅读0次

    十年前,在刚解除java不久,面试中就有人问道LinkedList和ArrayList的区别。我记得当时普片的答案都说是,LinikedList底层基于链表实现。而ArrayList底层基于数据。因此LinkedList的查找操作比ArrayList慢,但是增删等操作由于不需要移动数据,因此会比ArrayList快。但是事实上果真如此吗?对此,我们对ArrayList和LinkedList的多种情景进行分析。
    定义的全局变量如下:

    public static final int initSize = 100000;
    public static final int changeSize = 400000;
    
    private static ArrayList<Integer> arrayList = new ArrayList<>(initSize+changeSize);
    private static LinkedList<Integer> linkedList = new LinkedList<>();
    
    private static final Random random = new Random();
    

    另外还有一个初始化方法,使用之前先对List初始化:

    private static void initElems() {
        arrayList.clear();
        IntStream.range(0,initSize).forEach(i -> {
            arrayList.add(i);
            linkedList.add(i);
        });
    }
    

    1.插入分析

    对于一个集合,我们最常用的方式是向其中插入数据,常用的方法有头插和尾插还有随机插入三种方法,现在根据这三种方法分别进行分析。为了真实有用,我们定义了一个初始大小为10000。

    1.1 尾插法

    尾插法就是在列表的末尾插入一个元素,代码如下:

    private static void tailInsert(ArrayList arrayList,int size) {
        long start = System.currentTimeMillis();
        IntStream.range(0,size).forEach(i -> {
            arrayList.add(i);
        });
        long end = System.currentTimeMillis();
        System.out.println("ArrayList tailInsert cost:"+(end-start)+"ms");
    }
    
    private static void tailInsert(LinkedList linkedList,int size) {
        long start = System.currentTimeMillis();
        IntStream.range(0,size).forEach(i -> {
            linkedList.add(i);
        });
        long end = System.currentTimeMillis();
        System.out.println("LinkedList tailInsert cost:"+(end-start)+"ms");
    }
    

    我们只需修改changeSize的大小,然后再对结果进行比较,测试之后结果如下(时间单位ms):

    数据量 10万 20万 40万 80万 160万 320万 640万
    ArrayList 5 8 8 14 29 125 3528 3209
    LinkedList 4 8 11 21 266 3575 7179

    可以看出,在数据量较小的时候,LinkedList还是有优势的。随着数据量的增大,LinkedList的性能几乎是ArrayList的二倍以上。

    1.2 头插法

    我们再修改为对一个集合头部进行插入:

    private static void headInsert(ArrayList arrayList,int size) {
        long start = System.currentTimeMillis();
        IntStream.range(0,size).forEach(i -> {
            arrayList.add(0,i);
        });
        long end = System.currentTimeMillis();
        System.out.println("ArrayList headInsert cost:"+(end-start)+"ms");
    }
    
    private static void headInsert(LinkedList linkedList,int size) {
        long start = System.currentTimeMillis();
        IntStream.range(0,size).forEach(i -> {
            linkedList.add(0,i);
        });
        long end = System.currentTimeMillis();
        System.out.println("LinkedList headInsert cost:"+(end-start)+"ms");
    }
    

    我们修改changeSize的大小,然后再对结果进行比较,测试之后结果如下(时间单位ms):

    数据量 10万 20万 40万 80万
    ArrayList 1967 4467 12813 45214
    LinkedList 6 9 14 26

    可以看出,LinkedList在头部插入的时候能够充分发挥其链表的优点。性能非常高。

    1.3 随机插入

    实际上在使用List的过程中还有一种方式是随机插入数据。

    private static void randomInsert(ArrayList arrayList,int size) {
        long start = System.currentTimeMillis();
        IntStream.range(0,size).forEach(i -> {
            arrayList.add(random.nextInt(initSize+i),i);
        });
        long end = System.currentTimeMillis();
        System.out.println("ArrayList randomInsert cost:"+(end-start)+"ms");
    }
    
    private static void randomInsert(LinkedList linkedList,int size) {
        long start = System.currentTimeMillis();
        IntStream.range(0,size).forEach(i -> {
            linkedList.add(random.nextInt(initSize+i),i);
        });
        long end = System.currentTimeMillis();
        System.out.println("LinkedList randomInsert cost:"+(end-start)+"ms");
    }
    

    测试之后结果如下(时间单位ms):

    数据量 10万 20万 40万 80万
    ArrayList 880 2291 6663 21644
    LinkedList 30589 199670 耗时太长 耗时太长

    可以看到,对于随机插入,LinkedList几乎完全不能用。ArrayList在此种情况下性能还是不错的。

    2.反转

    对list还有一个常用的操作就是进行反转操作。

    private static void reverse(ArrayList arrayList) {
        long start = System.currentTimeMillis();
        Collections.reverse(arrayList);
        long end = System.currentTimeMillis();
        System.out.println("ArrayList reverse cost:"+(end-start)+"ms");
    }
    
    
    private static void reverse(LinkedList linkedList) {
        long start = System.currentTimeMillis();
        Collections.reverse(linkedList);
        long end = System.currentTimeMillis();
        System.out.println("LinkedList reverse cost:"+(end-start)+"ms");
    }
    
    

    我们修改initSize的大小,然后再对结果进行比较,测试之后结果如下(时间单位ms):

    数据量 10万 20万 40万 80万 160万 320万 640万
    ArrayList 2 5 6 10 11 12 13
    LinkedList 7 9 12 15 20 44 68

    可以看到,虽然两个List对反转的处理性能都还可以,但是明显可以发现还是ArrayList的性能高。

    3.分析

    根据上述得到的结果进行分析,不难发现,除了头部插入这一种方法之外,其他的场景LinkedList的性能都低于ArrayList。
    这是因为,LinkedList虽然修改操作只用改变前后节点的指针,而ArrayList由于需要在很多情况下通过System.arraycopy操作来创建新数组来实现将后续的元素挪动。看上去可能ArrayList的性能会低。但是实际上,在LinkedList中,对需要操作的节点寻址只能扫描全部的数据。而头插法恰恰是在头部操作不需要寻址,因此将LinkedList的优势发挥出来了。其他场景都不如ArrayList。
    而这个寻址的方法即使我们之前提到的node方法:

        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;
            }
        }
        
           public void add(int index, E element) {
            checkPositionIndex(index);
    
            if (index == size)
                linkLast(element);
            else
                linkBefore(element, node(index));
        }
    

    在add方法中,如果index与size不等,则不得不用node方法找到索引位置的节点。

    4.总结

    经过以上分析我们可以得出,LinkedList的增删性能还真不一定比ArrayList快,在某些场景下可能还会比ArrayList低。因此,我们在使用的时候需要对其使用场景进行了解。通常情况下ArrayList的性能都好于ArrayList。
    而LinkedList的头插法比ArrayList高效,这实际上也是队列的一种基本用法。实际上LinKedList本身也是一个Deque结构。
    对于LinkedList,其作者也在twitter上回复:


    image.png

    实际上LinkedList不会有人真的用。

    相关文章

      网友评论

        本文标题:你不知道的LinkedList(二):LinkedList的增删

        本文链接:https://www.haomeiwen.com/subject/cdafjktx.html