第八章 ArrayList 与 LinkedList 的使用选择分析

前言

面试官常问的一个问题是ArrayList 和 LinkedList 的区别是什么?
百度:ArrayList 和 LinkedList 的区别是什么? 也会出现一大堆。
这是一个老生常谈,白谈不厌的问题。本章作为学习的一章,将来分析这个话题。
ArrayList 与 LinkedList 之 我 见。
下面备上前面两章的源码分析地址:
第六章 JAVA的集合之ArrayList源码分析
第七章 JAVA的集合之LinkedList源码分析

分析主要还是通过两个集合的【增】【删】【查】等等的操作行为入手,分析它们的时间复杂度和空间复杂度,在不同情况下的优缺点,以便后面在选用时能做到更好的选择。

一、 增加

ArrayList:

add(E e)
add(int index, E element)
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c)

LinkedList:

addFirst(E e)
addLast(E e)
add(E e)
add(int index, E element)
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c)

从上面两组Api可以分析得到,增加主要从增加数量和增加位置两个维度在变化。
数量:单个,多个
位置:首、尾、中间。
其两个维度可以组成6种组合。由于单个和多个的操作是一样,不影响时间复杂度,因此只分析位置。

分析背景,两者目前都有n个元素。

a. 在首位增加
ArrayList :采用的方法是 add(int index, E element),由于其底层是数组结构,当在首位添加元素时,其后的n位元素均需要向后移动一位,其时间复杂度是O(n),除了这个移动的时间消耗外,如果涉及到增加元素时,容量不够,还要考虑数组扩容带来的消耗。

LinkedList:采用的方法是addFirst(E e),其需要将原来的首位元素解链,加上新的元素结点即可,因此它的时间复杂度是O(1),不会随着容器中元素个数增大而增大。
实验

public static void main(String[] args)  {

// arrayList
    long s = System.currentTimeMillis();
    ArrayList arrayList = new ArrayList();
    for (int i = 0; i < 150000; i++) {
        arrayList.add(0,i);
    }
    long b = System.currentTimeMillis();
    System.out.println(b-s);
    
// linkedList
    LinkedList linkedList = new LinkedList();
    for (int i = 0; i < 150000; i++) {
        linkedList.add(0,i);
    }
    long c = System.currentTimeMillis();
    System.out.println(c-b);
}

结果:2298 、6

这一轮PK结果: linkedList  优于 arrayList

b. 在末尾增加
ArrayList :采用的方法是 add(E e),由于只在末尾添加,不涉及到元素的移动,因此其时间复杂度是O(1),如果涉及到增加元素时,容量不够,要考虑数组扩容带来的消耗。

LinkedList:采用的方法是addLast(E e),其需要将原来的末尾的元素解链,加上新的元素结点即可,不同的是,末尾的元素不需要通过从头开始索引寻找,因为LInkedList内部维持着指向链表首尾的指针。所以与addFirst(E e)同样,因此它的时间复杂度是O(1)。

实验

 public static void main(String[] args)  {
 
    long s = System.currentTimeMillis();
    ArrayList arrayList = new ArrayList();
    for (int i = 0; i < 1500000; i++) {
        arrayList.add(i);
    }
    long b = System.currentTimeMillis();
    System.out.println(b-s);

    LinkedList linkedList = new LinkedList();
    for (int i = 0; i < 1500000; i++) {
        linkedList.add(i);
    }
    long c = System.currentTimeMillis();
    System.out.println(c-b);
}

结果:58 、 17

这一轮PK结果:    arrayList  略逊于  linkedList

c. 在中间增加
ArrayList :采用的方法是 add(int index, E element),由于其底层是数组结构,通过下标获取元素的的时间复杂度为O(1),但是由于增加位置在中间index处,所以index下标后的所有元素(size-index)个,都需要向后移动,其时间复杂度为O(n),如果涉及到增加元素时,容量不够,要考虑数组扩容带来的消耗。

LinkedList:采用的方法是add(int index, E element),由于其底层是链表结构,通过下标获取元素的时间复杂度为O(n),其增加位置在中间,与首尾增加的操作是一样的,其时间复杂度是O(1)。

实验

public static void main(String[] args)  {

    long s = System.currentTimeMillis();
    ArrayList arrayList = new ArrayList();
    for (int i = 0; i < 150000; i++) {
        int index = 0;
        if (i>2){
            index = i/3;
        }
        arrayList.add(index,i);
    }
    long b = System.currentTimeMillis();
    System.out.println(b-s);

    LinkedList linkedList = new LinkedList();
    for (int i = 0; i < 150000; i++) {
        int index = 0;
        if (i>2){
            index = i/3;
        }
        linkedList.add(index,i);
    }
    long c = System.currentTimeMillis();
    System.out.println(c-b);
}

结果:1387、14626

这一轮PK结果:   
实验结果 arrayList 优于 linkedList  原因不详,还望高手指点。

对于增加行为而言。如果在首位频繁添加,则考虑LInkedList,其他的位置则可以综合其他方面考虑。

二 删除

两者作为集合,其功能就是用于存储数据,因此少不了增添和删除。对于两者来讲,其删除功能与增加功能是一样的,如果以面向对象的角度来看,增加和删除都是属于操作行为,操作是抽象类,增加与删除是操作的实现子类。

对于增加或者删除而言,操作前的准备都是寻找位置,ArrayList与LinkedList不同之处在于底层结构一方是数组,其索引的时间复杂度为O(1),数组的删除和增加需要考虑位置的移动和数组的容量,而链表则不需要考虑。另一方是链表,其索引的时间复杂度为O(n)。因此对于删除和增加而言,其行为结果是一致的,下面简单描述一下删除。

a. 在头部删除
对于头部删除,其索引的时间复杂度都是O(1),数组删除后需要移动位置,而链表不需要。

b. 在中间删除
对于中间删除,其索引时间复杂度和在中间增加一致。操作也是一致,不再讨论。

c. 在末尾删除
对于末尾删除,与末尾增加一致。时间复杂度都是O(1),操作也是一致,不再讨论。

三 索引

两者涉及索引的方法有以下几个:

	get(int index)
	indexOf(Object o)
	contains(Object o)

arrayList 的get(int index)方法,由于其底层是数组结构,通过下标获取元素的时间复杂度为O(1),而LinkedList 除了头部和尾部,其他结点索引的时间复杂度是O(n),

indexOf(Object o)是通过元素比对来获取下标,其内部需要通过遍历,时间复杂度都是O(n)

contains(Object o)方法,由于需要比对元素,其内部调用的其实是IndexOf()方法,因此它们的时间复杂度都是O(n)。

结论

综上所述,两者的区别主要在其内部的组成结构一个是数组,一个是链表。数组与链表最大的不同就是其索引带来的时间复杂度不同,另一个就是数组的增删操作需要考虑元素的移动。
如果操作元素的增删频率不高,而对元素的查询、获取的频率较高,则可以考虑arrayList。

如果操作元素的增删频率较高,对元素的查询获取要求不高时,则要分析其操作位置
a.如果是操作主要位置是在头部,则考虑用linkedList
b.如果是操作主要位置是在中间位置,则可以考虑ArrayList
b.如果是操作主要位置是在尾部,则LinkedList 和 arrayList相差不大,具体选择可以侧重其他附带功能

猜你喜欢

转载自blog.csdn.net/weixin_43901067/article/details/105052559
今日推荐