java面试系列(1)—— ArrayList与LinkedList的区别

说一下ArrayList与LinkedList的区别

1.首先,他们的底层实现结构不同,ArrayList基于数组,LinkedList基于链表实现。
2.由于底层结构的不同,ArrayList更适合随机查找,LinkedList更适合添加、删除。他们查找、添加、删除在不同的场景下时间复杂读有所不同。
3.另外ArrayList和LinkedList都实现了List等接口,但是LinkedList还实现了Deque接口,所以LinkedList还可以用作队列。

以下是详细解析(只展示部分源码):

一.ArrayList基于数组,LinkedList基于链表

下面是展示ArrayList是基于数组的部分依据

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
	
    private static final long serialVersionUID = 8683452581122892189L;

    
    private static final int DEFAULT_CAPACITY = 10;

   
    private static final Object[] EMPTY_ELEMENTDATA = {};

    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

   //ArrayList底层的数组结构
    transient Object[] elementData; // non-private to simplify nested class access

   //数组中存放了多少元素,不是数组的长度。
    private int size;
    }

下面是LinkedList基于链表的部分依据:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
	//链表的长度
    transient int size = 0;
    
    //链表的头结点
    transient Node<E> first;
    
    //链表的尾结点
    transient Node<E> last;

	//将结点定义为LinkedList的内部类,
	private static class Node<E> {
		//从结构看出这是一个双向链表,维护了当前结点的下一个和上一个指针
	        E item;
	        Node<E> next;
	        Node<E> prev;
	
	        Node(Node<E> prev, E element, Node<E> next) {
	            this.item = element;
	            this.next = next;
	            this.prev = prev;
	        }
	    }
}

二.ArrayList与LinkedList的查询操作

两个集合都有一个查询操作的方法

  • get(int index)

对于ArrayList,因为其是基于数组实现的,根据索引可以直接查询到对应的数据,其时间复杂度为O(1)

//ArrayList
public E get(int index) {
		//检查index是否合法
        rangeCheck(index);
		//数组下标返回元素
        return elementData(index);
    }

对于LinkedList,查找对应的index需要对链表进行遍历,如果index=0或index=size()-1 ,那么其查询时间复杂度与ArrayList的查询复杂度一样都为O(1)。当index越向链表长度的中间点靠近,其查询效率就会越来越低,最终达到O(n)。

//LinkedList
public E get(int index) {
		//检查index的合法性
        checkElementIndex(index);
        //查找对应index的结点
        return node(index).item;
    }
    
    //如果index处于链表的前半部分,则从头结点开始遍历;如果index处于链表的后半段,则从尾结点向前遍历。基础是因为其为双向链表
 Node<E> node(int index) {
		//size>>1  相当于   size/2

        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;
        }
    }
  • 总结:ArrayList查询复杂度为O(1),LinkedList的查询复杂度为O(n)。但是对于LinkedList如果查询的元素在头结点或尾结点,其时间复杂度为O(1)。ArrayList比LinkedList更适合查询。

三.ArrayList与LinkedList的增、删操作

因为增添和删除是两个相对立的操作,其时间复杂度有相似的地方,所以我们这里采用增添方法来对比两个集合的区别

add(E e) 添加元素到末尾

  • ArrayList: 没有指定下标,默认将元素放到数组的最后,不需要移动元素,时间复杂度为O(1)
 public boolean add(E e) {
 		//确保数组的大小还能够放下一个元素
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //数组末尾放元素
        elementData[size++] = e;
        return true;
    }
  • LinkedList: 没有指定下标,默认添加到链表的最后,不需要查找指定的index,且链表是双向链表,维护了尾结点,所以可以直接在尾结点进行增加元素,时间复杂度为O(1)。
public boolean add(E e) {
		
        linkLast(e);
        return true;
    }
    //在链表的末尾添加元素
    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;
		//链表的长度+1
        size++;
        //修改操作数+1  ,用于防止并发操作
        modCount++;
    }

add(int index,E e) 添加元素到指定下标

  • ArrayList :影响因素:扩容机制和arraycopy方法
    在ArrayList的数组大小足够大且添加元素在数组末尾时,其时间复杂度为O(1)。其他情况的时间复杂度为O(n)
public void add(int index, E element) {
        rangeCheckForAdd(index);
		//确保数组大小能否装下一个元素,否则实现扩容机制
        ensureCapacityInternal(size + 1);  // Increments modCount!!

		//将[index,size()-1]的数据移动(复制)到[index+1,size()] 的位置
		//这里时间复杂度为O(n)
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
         //直接对index进行赋值
        elementData[index] = element;
        size++;
    }
   
  • LinkedList :影响因素:node(index) 查找index所在的结点。除非添加的元素在链表的开头或结尾其时间复杂度为O(1)。否则其时间复杂度因为有查找index对应结点而为O(n)
public void add(int index, E element) {
		//检查下标index的合法性
        checkPositionIndex(index);
		//如果下标正好为尾结点下标+1,则表示在末尾添加元素
        if (index == size)
            linkLast(element);
        else
        //在链表中插入元素
            linkBefore(element, node(index));
    }
   //在链表末尾添加数据
 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++;
    }

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++;
    }
//根据下标查找对应的结点
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;
        }
    }

  • 总结:ArrayList更适合查找,LinkedList更适合增删,这由他们的底层数据结构决定,但是在不同的场景下,ArrayList与LinkedList各种操作的时间复杂度好坏不分上下,需要根据实际场景需要进行选择

四.ArrayList与LinkedList在Collection中的关系

java面试系列(1)—— ArrayList与LinkedList的区别_第1张图片
我们可以注意到ArrayList和LinkedList都实现了List接口,所以他们都是支持index进行访问数据的。另外LinkedList还实现了Queue接口,这就表示LinkedList还可以当作队列来使用,提供与队列相关的方法:

//LinkedList
addFirst(E e);
addLast(E e);
removeFirst();
removeLast();

//以上都是ArrayList不具有的方法

你可能感兴趣的:(java面试习题,java,面试,链表,集合)