你肯定 LinkedList 在新增/删除元素时,效率比 ArrayList 高?

在面试的时候都会被问到集合相关的问题,好比:你能讲讲 ArrayList 和 LinkedList 的区别吗?java

那么我相信你确定可以答上来:ArrayList 是基于数组实现的, LinkedList 是基于链表实现的node

接下来面试官就会连环问了,那你能讲讲,它们都用在什么场景下吗?web

阿粉知道这种程度确定难不倒我们读者的:由于 ArrayList 是基于数组实现的,因此在遍历的时候, ArrayList 的效率是要比 LinkedList 高的, LinkedList 是基于链表实现的,因此在进行新增/删除元素的时候, LinkedList 的效率是要比 ArrayList 高的面试

面试官:哦哦,好的,我大概了解了,我这边没有什么想问的了,您回去等消息能够吗数组

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

???发生了什么?微信

哈哈,上面模拟了一个面试场景,是想引出来这篇文章的主题:LinkedList 在新增/删除元素时,效率比 ArrayList 高,这是真的吗?app

我相信你也知道套路,通常这么一问,那确定就不是真的了dom

放一张图片,这是通过我测试以后的真实结果ide

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

由于微信不能放外链的缘故,能够在公众号后台发送 “20200821” 获取测试代码测试

ArrayList 与 LinkedList 新增元素比较

从图中能够看出来, LinkedList 在新增元素时,它的效率不必定比 ArrayList 高,这是要分状况的

若是是从集合头部位置新增元素的话,那确实是 LinkedList 的效率要比 ArrayList 高

可是若是是从集合中间位置或者是尾部位置新增元素, ArrayList 效率反而要比 LinkedList 效率要高

Excuse me ?居然和我之前学的不同?阿粉我学的浅,你别骗我

640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

哈哈哈,为何会这样呢

这是由于 ArrayList 是基于数组实现的嘛,而数组是一块连续的内存空间,因此在添加元素到数组头部时,须要对头部后面的数据进行复制重排,因此效率是蛮低的

可是 LinkedList 是基于链表实现的,在添加元素的时候,首先会经过循环查找到添加元素的位置,若是要添加的位置处于 List 前半段,那就从前向后找;若是位置在后半段,那就从后往前找,因此 LinkedList 添加元素到头部是很是高效的(小声 BB ,这我知道

哦,这你知道?看来基础蛮不错的嘛~

640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1

因此当 ArrayList 在添加元素到数组中间时,有一部分数据须要复制重排,效率就不是很高,那为啥 LinkedList 比它还要低呢?这是由于 LinkedList 把元素添加到中间位置的时候,须要在添加以前先遍历查找,这个查找的时间比较耗时

添加元素到尾部操做中, ArrayList 的效率要比 LinkedList 的还要高,这是为啥嘞

由于 ArrayList 在添加的时候不须要什么操做,直接插入就行了,因此效率蛮高的

可是 LinkedList 就不同了,对于 LinkedList 来讲,也不须要查找啥的,直接插入就能够了,可是须要 new 对象,还有变换指针指向对象呀,这些过程耗时加起来可就比 ArrayList 长了

它是有前提的,那就是 ArrayList 初始化容量是足够的状况下,才有上述的特色,若是 ArrayList 涉及到动态扩容,那它的效率确定会下降

ArrayList 与 LinkedList 删除元素比较

删除元素和新增元素的原理是同样的,因此删除元素的操做和新增元素的操做耗时也是很相近

这里就再也不赘述

ArrayList 与 LinkedList 遍历元素比较

测试结果很是明显,对于 LinkedList 来讲,若是使用 for 循环的话,效率特别低,可是 ArrayList 使用 for 循环去遍历的话,就比较高

为啥呢?

emmm ,得从源码提及

先来看 ArrayList 的源码吧,这个比较简单

public class ArrayList<Eextends AbstractList<E>
        implements List<E>, RandomAccessCloneablejava.io.Serializable

可以看到, ArrayList 实现了 List , RandomAccess , Cloneable 还有 Serializable 接口

你是否是对 RandomAccess 这个接口挺陌生的?这是个啥?

可是经过查阅源码可以发现它也只是个空的接口罢了,那 ArrayList 为啥还要去实现它嘞

由于 RandomAccess 接口是一个标志接口,它标识着“只要实现该接口的 list 类,均可以实现快速随机访问”

实现快速随机访问?你能想到什么?这不就是数组的特性嘛!能够直接经过 index 来快速定位 & 读取

那你是否是就能想到, ArrayList 是数组实现的,因此实现了 RandomAccess 接口, LinkedList 是用链表实现的,因此它没有用 RandomAccess 接口实现吧?

beautiful ~就是这样

咱瞅瞅 LinkedList 源码

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneablejava.io.Serializable

果真,跟我们设想的同样,没有实现 RandomAccess 接口

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

那为啥 LinkedList 接口使用 for 循环去遍历的时候,慢的不行呢?

我们瞅瞅 LinkedList 在 get 元素时,都干了点儿啥

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

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;
    }
}

在 get 方法中,主要调用了 node() 方法,由于 LinkedList 是双向链表,因此 if (index < (size >> 1)) 在判断 i 是在前半段仍是后半段,若是是前半段就正序遍历,若是是在后半段那就倒序遍历,那么为何使用 for 循环遍历 LinkedList 时,会这么慢?(好像离真相愈来愈近了

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

缘由就在两个 for 循环之中,以第一个 for 循环为例

  • get(0) :直接拿到了node0 地址,而后拿到 node0 的数据

  • get(1) :先拿到 node0 地址,而后 i < index ,开始拿 node1 的地址,符合条件,而后去拿 node1 的数据

  • get(2) :先拿到 node0 的地址,而后 i < index ,拿到 node1 的地址, i < index ,继续向下走,拿到 node2 的地址,符合条件,获取 node2 的数据

发现问题了嘛?我就是想要 2 的数据, LinkedList 在遍历时,将 0 和 1 也给遍历了,若是数据量很是大的话,那效率可不就唰唰的下来了嘛

那到如今,我们也就很是明确了,若是是要遍历 ArrayList 的话,最好是用 for 循环去作,若是要遍历 LinkedList 的话,最好是用迭代器去作

我猜你必定会说,阿粉啊,那若是对方就给我传过来了一个 list ,我不知道它是 ArrayList 仍是 LinkedList 呀?我该怎么办呢

还记得 ArrayList 和 LinkedList 有什么不一样吗?是否是 ArrayList 实现了 RandomAccess 接口,可是 LinkedList 没有实现,因此能够从这点去着手解决

暖暖的阿粉在这里给个简单的小 demo ,能够参考下:

public class ListTest {
    public static void main(String[] args) {
        List<String> arrayList = new ArrayList<String>();
        arrayList.add("aaa");
        arrayList.add("bbb");
        isUseIterator(arrayList);

        List<String> linkedList = new LinkedList<String>();
        linkedList.add("ccc");
        linkedList.add("ddd");
        isUseIterator(linkedList);
    }

    public static void isUseIterator(List list){
        if (list instanceof RandomAccess){
            System.out.println("实现了 RandomAccess 接口,使用 for 循环遍历");

            for (int i = 0 ; i < list.size(); i++ ){
                System.out.println(list.get(i));
            }
        }else{
            System.out.println("没有实现 RandomAccess 接口,使用迭代器遍历");

            Iterator it = list.iterator();
            while (it.hasNext()){
                System.out.println(it.next());
            }
        }
    }

因此,乖,下次面试官再问你 LinkedList 在新增/删除元素时,效率比 ArrayList 高吗,不要再傻傻的回答是了,拿阿粉这篇文章和他扯皮,保证没问题

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1