【面试题】说一下ArrayList和LinkedList的区别?

目录

前言

1. ArrayList分析

2. LinkedList分析

3. LinkedList插入速度比ArrayList快?你确定吗?

4. 数据结构

5. 源码分析

总结


前言

ArrayList 和 LinkedList 是 List 接口的两种不同实现,并且两者都不是线程安全的。但初学者往往搞不清楚它们两者之间的区别,不知道什么时候该用 ArrayList,什么时候该用 LinkedList,那这篇文章就来传道受业解惑一下。

ArrayList 内部使用动态数组来存储元素,LinkedList 内部使用的双向链表来存储元素,这也是 ArrayList 和 LinkedList 最本质的区别。

先来回顾一下List在Collection中的的框架图:


    从图中我们可以看出:

  1. List是一个接口,它继承与Collection接口,代表有序的队列。
  2. AbstractList是一个抽象类,它继承与AbstractCollection。AbstractList实现了List接口中除了size()、get(int location)之外的方法。
  3. AbstractSequentialList是一个抽象类,它继承与AbstrctList。AbstractSequentialList实现了“链表中,根据index索引值操作链表的全部方法”。
  4. ArrayList、LinkedList、Vector和Stack是List的四个实现类,其中Vector是基于JDK1.0,虽然实现了同步,但是效率低,已经不用了,Stack继承与Vector,所以不再赘述。
  5. LinkedList是个双向链表,它同样可以被当作栈、队列或双端队列来使用。

ArrayList和LinkedList区别

我们知道,通常情况下,ArrayList和LinkedList的区别有以下几点:

  1. ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构;
  2. 对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针;
  3. 对于添加和删除操作add和remove,一般大家都会说LinkedList要比ArrayList快,因为ArrayList要移动数据。但是实际情况并非这样,对于添加或删除,LinkedList和ArrayList并不能明确说明谁快谁慢,下面会详细分析。

1. ArrayList分析

1)get(int index) 方法的时间复杂度为 O ( 1 ) ,因为是直接从底层数组根据下标获取的,和数组长度无关。

public E get(int index) {
    Objects.checkIndex(index, size);
    return elementData(index);
}

这也是 ArrayList 的最大优点。

2)add(E e) 方法会默认将元素添加到数组末尾,但需要考虑到数组扩容的情况,如果不需要扩容,时间复杂度为 O ( 1 ) 。

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

如果需要扩容的话,并且不是第一次(oldCapacity > 0)扩容的时候,内部执行的 Arrays.copyOf() 方法是耗时的关键,需要把原有数组中的元素复制到扩容后的新数组当中。

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

3)add(int index, E element) 方法将新的元素插入到指定的位置,考虑到需要复制底层数组(根据之前的判断,扩容的话,数组可能要复制一次),根据最坏的打算(不管需要不需要扩容,System.arraycopy() 肯定要执行),所以时间复杂度为 O ( n ) 。

public void add(int index, E element) {
    rangeCheckForAdd(index);
    modCount++;
    final int s;
    Object[] elementData;
    if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();
    System.arraycopy(elementData, index,
            elementData, index + 1,
            s - index);
    elementData[index] = element;
    size = s + 1;
}

来执行以下代码,把沉默王八插入到下标为 2 的位置上。

ArrayList<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("沉默王四");
list.add("沉默王五");
list.add("沉默王六");
list.add("沉默王七");
list.add(2, "沉默王八");

System.arraycopy() 执行完成后,下标为 2 的元素为沉默王四,这一点需要注意。也就是说,在数组中插入元素的时候,会把插入位置以后的元素依次往后复制,所以下标为 2 和下标为 3 的元素都为沉默王四。

 之后再通过 elementData[index] = element 将下标为 2 的元素赋值为沉默王八;随后执行 size = s + 1,数组的长度变为 7。

 4)remove(int index) 方法将指定位置上的元素删除,考虑到需要复制底层数组,所以时间复杂度为 O(n)。

public E remove(int index) {
    Objects.checkIndex(index, size);
    final Object[] es = elementData;

    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);

    return oldValue;
}
private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}

2. LinkedList分析

1)get(int index) 方法的时间复杂度为 O(n),因为需要循环遍历整个链表。

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

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

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

下标小于链表长度的一半时,从前往后遍历;否则从后往前遍历,这样从理论上说,就节省了一半的时间。

如果下标为 0 或者 list.size() - 1 的话,时间复杂度为 O(1)。这种情况下,可以使用 getFirst() 和 getLast() 方法。

public E getFirst() {
    final LinkedList.Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

public E getLast() {
    final LinkedList.Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

first 和 last 在链表中是直接存储的,所以时间复杂度为 O(1)。

2)add(E e) 方法默认将元素添加到链表末尾,所以时间复杂度为 O(1)。

public boolean add(E e) {
    linkLast(e);
    return true;
}
void linkLast(E e) {
    final LinkedList.Node<E> l = last;
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

3)add(int index, E element) 方法将新的元素插入到指定的位置,需要先通过遍历查找这个元素,然后再进行插入,所以时间复杂度为 O(n)。

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

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

如果下标为 0 或者 list.size() - 1 的话,时间复杂度为 O(1)。这种情况下,可以使用 addFirst() 和 addLast() 方法。

public void addFirst(E e) {
    linkFirst(e);
}
private void linkFirst(E e) {
    final LinkedList.Node<E> f = first;
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

linkFirst() 只需要对 first 进行更新即可。

public void addLast(E e) {
    linkLast(e);
}

void linkLast(E e) {
    final LinkedList.Node<E> l = last;
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

linkLast() 只需要对 last 进行更新即可。

需要注意的是,有些文章里面说,LinkedList 插入元素的时间复杂度近似 O(1),其实是有问题的,因为 add(int index, E element) 方法在插入元素的时候会调用 node(index) 查找元素,该方法之前我们之间已经确认过了,时间复杂度为 O(n),即便随后调用 linkBefore() 方法进行插入的时间复杂度为 O(1),总体上的时间复杂度仍然为 O(n) 才对。

void linkBefore(E e, LinkedList.Node<E> succ) {
    // assert succ != null;
    final LinkedList.Node<E> pred = succ.prev;
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

4)remove(int index) 方法将指定位置上的元素删除,考虑到需要调用 node(index) 方法查找元素,所以时间复杂度为 O(n)。

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

E unlink(LinkedList.Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final LinkedList.Node<E> next = x.next;
    final LinkedList.Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

通过时间复杂度的比较,以及源码的分析,我相信小伙伴们在选择的时候就有了主意,对吧?

需要注意的是,如果列表很大很大,ArrayList 和 LinkedList 在内存的使用上也有所不同。LinkedList 的每个元素都有更多开销,因为要存储上一个和下一个元素的地址。ArrayList 没有这样的开销。

但是,ArrayList 占用的内存在声明的时候就已经确定了(默认大小为 10),不管实际上是否添加了元素,因为复杂对象的数组会通过 null 来填充。LinkedList 在声明的时候不需要指定大小,元素增加或者删除时大小随之改变。

另外,ArrayList 只能用作列表;LinkedList 可以用作列表或者队列,因为它还实现了 Deque 接口。

我在写这篇文章的时候,遇到了一些问题,所以请教了一些大厂的技术大佬,结果有个朋友说,“如果真的不知道该用 ArrayList 还是 LinkedList,就选择 ArrayList 吧!”

我当时以为他在和我开玩笑呢,结果通过时间复杂度的分析,好像他说得有道理啊。查询的时候,ArrayList 比 LinkedList 快,这是毋庸置疑的;插入和删除的时候,之前有很多资料说 LinkedList 更快,时间复杂度为O(1),但其实不是的,因为要遍历列表,对吧?

反而 ArrayList 更轻量级,不需要在每个元素上维护上一个和下一个元素的地址。

我这样的结论可能和大多数文章得出的结论不符,那么我想,选择权交给小伙伴们,你们在使用的过程中认真地思考一下,并且我希望你们把自己的思考在留言区放出来。

3. LinkedList插入速度比ArrayList快?你确定吗?

小傅哥 bugstack.cn & ArrayList头插、尾插、中间

小傅哥 bugstack.cn & LinkedList头插、尾插、中间

如图,分别是;10万100万1000万,数据在两种集合下不同位置的插入效果,所以:,不能说LinkedList插入就快,ArrayList插入就慢,还需要看具体的操作情况。

接下来我们带着数据结构和源码,具体分析下。

4. 数据结构

Linked + List = 链表 + 列表 = LinkedList = 链表列表

小傅哥 bugstack.cn & LinkedList数据结构

LinkedList,是基于链表实现,由双向链条next、prev,把数据节点穿插起来。所以,在插入数据时,是不需要像我们上一章节介绍的ArrayList那样,扩容数组。

但,又不能说所有的插入都是高效,比如中间区域插入,他还需要遍历元素找到插入位置。具体的细节,我们在下文的源码分析中进行讲解,也帮谢飞机扫除疑惑。

5. 源码分析

1. 初始化

与ArrayList不同,LinkedList初始化不需要创建数组,因为它是一个链表结构。而且也没有传给构造函数初始化多少个空间的入参,例如这样是不可以的,如下;

但是,构造函数一样提供了和ArrayList一些相同的方式,来初始化入参,如下这四种方式;

@Test
public void test_init() {
    // 初始化方式;普通方式
    LinkedList<String> list01 = new LinkedList<String>();
    list01.add("a");
    list01.add("b");
    list01.add("c");
    System.out.println(list01);
    
    // 初始化方式;Arrays.asList
    LinkedList<String> list02 = new LinkedList<String>(Arrays.asList("a", "b", "c"));
    System.out.println(list02);
    
    // 初始化方式;内部类
    LinkedList<String> list03 = new LinkedList<String>()\\{
        {add("a");add("b");add("c");}
    \\};
    System.out.println(list03);
    
    // 初始化方式;Collections.nCopies
    LinkedList<Integer> list04 = new LinkedList<Integer>(Collections.nCopies(10, 0));
    System.out.println(list04);
}

// 测试结果

[a, b, c]
[a, b, c]
[a, b, c]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Process finished with exit code 0
  • 这些方式都可以初始化操作,按需选择即可。

2. 插入

LinkedList的插入方法比较多,List中接口中默认提供的是add,也可以指定位置插入。但在LinkedList中还提供了头插addFirst和尾插addLast

关于插入这部分就会讲到为什么;有的时候LinkedList插入更耗时、有的时候ArrayList插入更好。

2.1 头插

先来看一张数据结构对比图,回顾下ArrayList的插入也和LinkedList插入做下对比,如下;

小傅哥 bugstack.cn & 插入对比

看上图我们可以分析出几点;

  1. ArrayList 头插时,需要把数组元素通过Arrays.copyOf的方式把数组元素移位,如果容量不足还需要扩容。
  2. LinkedList 头插时,则不需要考虑扩容以及移位问题,直接把元素定位到首位,接点链条链接上即可。

2.1.1 源码

这里我们再对照下LinkedList头插的源码,如下;

private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}
  • first,首节点会一直被记录,这样就非常方便头插。
  • 插入时候会创建新的节点元素,new Node<>(null, e, f),紧接着把新的头元素赋值给first。
  • 之后判断f节点是否存在,不存在则把头插节点作为最后一个节点、存在则用f节点的上一个链条prev链接。
  • 最后记录size大小、和元素数量modCount。modCount用在遍历时做校验,modCount != expectedModCount

2.1.2 验证

ArrayList、LinkeList,头插源码验证

@Test
public void test_ArrayList_addFirst() {
    ArrayList<Integer> list = new ArrayList<Integer>();
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++) {
        list.add(0, i);
    }
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

@Test
public void test_LinkedList_addFirst() {
    LinkedList<Integer> list = new LinkedList<Integer>();
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++) {
        list.addFirst(i);
    }
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

比对结果:

  • 这里我们分别验证,10万、100万、1000万的数据量,在头插时的一个耗时情况。
  • 如我们数据结构对比图中一样,ArrayList需要做大量的位移和复制操作,而LinkedList的优势就体现出来了,耗时只是实例化一个对象。

2.2 尾插

先来看一张数据结构对比图,回顾下ArrayList的插入也和LinkedList插入做下对比,如下;

小傅哥 bugstack.cn & 插入对比

看上图我们可以分析出几点;

  1. ArrayList 尾插时,是不需要数据位移的,比较耗时的是数据的扩容时,需要拷贝迁移。
  2. LinkedList 尾插时,与头插相比耗时点会在对象的实例化上。

2.2.1 源码

这里我们再对照下LinkedList尾插的源码,如下;

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
  • 与头插代码相比几乎没有什么区别,只是first换成last
  • 耗时点只是在创建节点上,Node<E>

2.2.2 验证

ArrayList、LinkeList,尾插源码验证

@Test
public void test_ArrayList_addLast() {
    ArrayList<Integer> list = new ArrayList<Integer>();
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++) {
        list.add(i);
    }
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

@Test
public void test_LinkedList_addLast() {
    LinkedList<Integer> list = new LinkedList<Integer>();
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        list.addLast(i);
    }
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

比对结果:

  • 这里我们分别验证,10万、100万、1000万的数据量,在尾插时的一个耗时情况。
  • 如我们数据结构对比图中一样,ArrayList 不需要做位移拷贝也就不那么耗时了,而LinkedList则需要创建大量的对象。所以这里ArrayList尾插的效果更好一些。

2.3 中间插

先来看一张数据结构对比图,回顾下ArrayList的插入也和LinkedList插入做下对比,如下;

看上图我们可以分析出几点;

  1. ArrayList 中间插入,首先我们知道他的定位时间复杂度是O(1),比较耗时的点在于数据迁移和容量不足的时候扩容。
  2. LinkedList 中间插入,链表的数据实际插入时候并不会怎么耗时,但是它定位的元素的时间复杂度是O(n),所以这部分以及元素的实例化比较耗时。

2.3.1 源码

这里看下LinkedList指定位置插入的源码;

使用add(位置、元素)方法插入:

public void add(int index, E element) {
    checkPositionIndex(index);
    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

位置定位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;
    }
}
  • size >> 1,这部分的代码判断元素位置在左半区间,还是右半区间,在进行循环查找。

执行插入:

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}
  • 找到指定位置插入的过程就比较简单了,与头插、尾插,相差不大。
  • 整个过程可以看到,插入中比较耗时的点会在遍历寻找插入位置上。

2.3.2 验证

ArrayList、LinkeList,中间插入源码验证

@Test
public void test_ArrayList_addCenter() {
    ArrayList<Integer> list = new ArrayList<Integer>();
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000000; i++) {
        list.add(list.size() >> 1, i);
    }
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

@Test
public void test_LinkedList_addCenter() {
    LinkedList<Integer> list = new LinkedList<Integer>();
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        list.add(list.size() >> 1, i);
    }
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

比对结果:

  • 这里我们分别验证,10万、100万、1000万的数据量,在中间插时的一个耗时情况。
  • 可以看到Linkedlist在中间插入时,遍历寻找位置还是非常耗时了。所以不同的情况下,需要选择不同的List集合做业务。

3. 删除

讲了这么多插入的操作后,删除的知识点就很好理解了。与ArrayList不同,删除不需要拷贝元素,LinkedList是找到元素位置,把元素前后链连接上。基本如下图;

  • 确定出要删除的元素x,把前后的链接进行替换。
  • 如果是删除首尾元素,操作起来会更加容易,这也就是为什么说插入和删除快。但中间位置删除,需要遍历找到对应位置。

3.1 删除操作方法

序号方法描述
1list.remove();与removeFirst()一致
2list.remove(1);删除Idx=1的位置元素节点,需要遍历定位
3list.remove("a");删除元素="a"的节点,需要遍历定位
4list.removeFirst();删除首位节点
5list.removeLast();删除结尾节点
6list.removeAll(Arrays.asList("a", "b"));按照集合批量删除,底层是Iterator删除

源码:

@Test
public void test_remove() {
    LinkedList<String> list = new LinkedList<String>();
    list.add("a");
    list.add("b");
    list.add("c");
    
    list.remove();
    list.remove(1);
    list.remove("a");
    list.removeFirst();
    list.removeLast();
    list.removeAll(Arrays.asList("a", "b"));
}

3.2 源码

删除操作的源码都差不多,分为删除首尾节点与其他节点时候,对节点的解链操作。这里我们举例一个删除其他位置的源码进行学习,如下;

list.remove("a");

public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}
  • 这一部分是元素定位,和unlink(x)解链。循环查找对应的元素,这部分没有什么难点。

unlink(x)解链

E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }
    
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }
    
    x.item = null;
    size--;
    modCount++;
    return element;
}

这部分源码主要有以下几个知识点;

  1. 获取待删除节点的信息;元素item、元素下一个节点next、元素上一个节点prev。
  2. 如果上个节点为空则把待删除元素的下一个节点赋值给首节点,否则把待删除节点的下一个节点,赋值给待删除节点的上一个节点的子节点。
  3. 同样待删除节点的下一个节点next,也执行2步骤同样操作。
  4. 最后是把删除节点设置为null,并扣减size和modeCount数量。

4. 遍历

接下来说下遍历,ArrayList与LinkedList的遍历都是通用的,基本包括5种方式。

这里我们先初始化出待遍历的集合1千万数据;

int xx = 0;
@Before
public void init() {
    for (int i = 0; i < 10000000; i++) {
        list.add(i);
    }
}

4.1 普通for循环

@Test
public void test_LinkedList_for0() {
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < list.size(); i++) {
        xx += list.get(i);
    }
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

4.2 增强for循环

@Test
public void test_LinkedList_for1() {
    long startTime = System.currentTimeMillis();
    for (Integer itr : list) {
        xx += itr;
    }
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

4.3 Iterator遍历

@Test
public void test_LinkedList_Iterator() {
    long startTime = System.currentTimeMillis();
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
        Integer next = iterator.next();
        xx += next;
    }
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime))
}

4.4 forEach循环

@Test
public void test_LinkedList_forEach() {
    long startTime = System.currentTimeMillis();
    list.forEach(integer -> {
        xx += integer;
    });
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

4.5 stream(流)

@Test
public void test_LinkedList_stream() {
    long startTime = System.currentTimeMillis();
    list.stream().forEach(integer -> {
        xx += integer;
    });
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

那么,以上这5种遍历方式谁最慢呢?按照我们的源码学习分析下吧,欢迎留下你的答案在评论区!

总结

  • ArrayList与LinkedList都有自己的使用场景,如果你不能很好的确定,那么就使用ArrayList。但如果你能确定你会在集合的首位有大量的插入、删除以及获取操作,那么可以使用LinkedList,因为它都有相应的方法;addFirstaddLastremoveFirstremoveLastgetFirstgetLast,这些操作的时间复杂度都是O(1),非常高效。
  • LinkedList的链表结构不一定会比ArrayList节省空间,首先它所占用的内存不是连续的,其次他还需要大量的实例化对象创造节点。虽然不一定节省空间,但链表结构也是非常优秀的数据结构,它能在你的程序设计中起着非常优秀的作用,例如可视化的链路追踪图,就是需要链表结构,并需要每个节点自旋一次,用于串联业务。
  • 程序的精髓往往就是数据结构的设计,这能为你的程序开发提供出非常高的效率改变。可能目前你还不能用到,但万一有一天你需要去造🚀火箭了呢?

参考文章:

java集合框架05——ArrayList和LinkedList的区别_武哥聊编程的博客-CSDN博客_arraylist和linkedlist的区别

阿里面试官:说一下ArrayList和LinkedList的区别?_沉默王二的博客-CSDN博客_arraylist和linkedlist的区别

ArrayList 和 LinkedList 性能对比 - 简书

面经手册 · 第8篇《LinkedList插入速度比ArrayList快?你确定吗?》 - 小傅哥 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慕城南风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值