Java中ArrayList和LinkedList區別

1 前言

許多語言,例如 Perl ,Python 和 Ruby ,都有集合的本地支持。有些語言(例如Python)甚至將基本集合組件(列表,映射和集合)作為內置函數包含在其中。

通常,程序總是根據運行時才知道的某些條件去創建新的對象。在此之前,無法知道所需對象的數量甚至確切類型。為瞭解決這個普遍的編程問題,需要在任意時刻和任意位置創建任意數量的對象。java.util 庫提供瞭一套相當完整的集合類(collection classes)來解決這個問題,其中基本的類型有 List 、 Set 、 Queue 和 Map。這些類型也被稱作容器類。

2 數據結構的區別

2.1 ArrayList

ArrayList是基於數組實現的,數組是典型的緊密結構,所以它使用索引在數組中搜索和讀取數據快,可以直接返回數組中index位置的元素,因此在隨機訪問集合元素上有較好的性能。

2.2 LinkedList

LinkedList是基於雙鏈表實現的,鏈表是典型的跳轉結構,插入和刪除隻是指針指向的修改,所以它插入、刪除中間元素快,因此在操作數據方面性能較好。

2.3 使用場景

如果應用程序對數據有較多的隨機訪問,ArrayList對象要優於LinkedList對象;

如果應用程序有更多的插入或者刪除操作,較少的隨機訪問,LinkedList對象要優於ArrayList對象;

3 源碼分析

下面我們通過JDK1.8源碼源碼分析一下核心功能。

3.1 ArrayList核心源碼

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    
    //默認大小
    private static final int DEFAULT_CAPACITY = 10;
     
    //省略。。。
    
    //動態數組
    transient Object[] elementData; 
    
    //數組大小
    private int size;
    
    //空構造器
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    // 查詢元素
    public E get(int index) {
        // 檢查索引是否在范圍內
        rangeCheck(index);                    
        return elementData(index);
    }
    
    //順序添加元素
    public boolean add(E e) {
        //擴容
        ensureCapacityInternal(size + 1); 
        elementData[size++] = e;
        return true;
    }
    
    //從數組中間添加元素
    public void add(int index, E element) {
        // 檢查索引是否在范圍內
        rangeCheckForAdd(index);
        // 擴容
        ensureCapacityInternal(size + 1); 
        // 復制數組
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 替換元素
        elementData[index] = element;
        size++;
    }
    
    //是否要擴容
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    //確定容量
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    //計算數組容量
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    //動態數組擴容放法
    private void grow(int minCapacity) {
        // 
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 數組復制
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
     //省略。。。
    
  }

總結:

ArrayLis初始化構造器的時候數組為{},在調用add方法以後默認數組才賦值為新數組,新數組默認長度為10。
通過擴容機制判斷原數組是否還有空間,若沒有則重新實例化一個空間更大的新數組,把舊數組的數據拷貝到新數組中。
在執行查詢操作時,先判斷下標是否越界,然後在直接通過下標從數組中返回元素。

3.2 LinkedList核心源碼

//JDK1.8
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;
    
    // 空構造器
    public LinkedList() {
        
    }
    
    // Node內部類
    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;
        }
    }
    
    // 順序添加元素
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
    // 指定位置添加元素
    public void add(int index, E element) {
        // 檢查是否越界
        checkPositionIndex(index);    
        // 在鏈表末尾添加,否則在之前插入元素
        if (index == size)            
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    
    // 添加元素e
    void linkLast(E e) {
        //把鏈表中last元素賦給l ,如果是第一個元素則為null
        final Node<E> l = last;
        //把元素封裝為一個Node對象
        final Node<E> newNode = new Node<>(l, e, null);
        //把鏈表的last元素指向新的元素
        last = newNode;
        //如果添加的是第一個元素
        if (l == null){
            //把鏈表的first元素指向新的元素
            first = newNode;
        }
        //如果添加的不是第一個元素
        else{
            //把l的下一個元素指向新的元素
            l.next = newNode;            
        }
        //集合中元素數量加1
        size++;
        modCount++;
    }
    
    void linkBefore(E e, Node<E> succ) {
        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++;
    }
    
    //獲取元素
    public E get(int index) {
        //檢測元素索引
        checkElementIndex(index);
        return node(index).item;
    }
    
    //返回指定元素索引處的(非空)節點
    Node<E> node(int index) {
        //把鏈表分為兩段,如果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;
        }
    }
    
    //省略。。。
    
}

總結:

LinkedList的get首先判斷需要獲取的數據在該鏈表的前半段還是後半段,這樣可以減少需要遍歷的數據,由於需要遍歷數據,所以獲取數據的速度會比ArrayList慢。
由於LinkedList底層是用雙向鏈表實現的,沒有初始化大小,也沒有擴容的機制。

4 碼農來洞見

4.1為什麼ArrayList比LinkedList要快

ArrayListLinkedList本質上每次插入和刪除元素都會進行復制數組的操作,如果ArrayList插入操作沒有觸發數組擴容操作,並且在List靠近末尾的地方插入,那麼ArrayList隻需要移動較少的數據,而LinkedList則需要一直查找到列表尾部,反而耗費較多時間,這時ArrayList就比LinkedList要快。

4.2 註意ArrayList不同JDK版本源碼

JDK1.7中在調用構造器的時候數組長度就初始化為瞭10,JDK1.8則是在調用add方法後才創建數組長度為10的新數組替換默認空數組。

4.3 高並發下如何保證集合數據的同步

ArrayListLinkedList都不是線程安全的。然而Vector類被認為是過時的廢棄的瞭。

方案一: Collections.synchronizedList(); 得到一個線程安全的 ArrayList。

方案二: concurrent 並發包下的 CopyOnWriteArrayList 類。

CopyOnWriteArrayList和Collections.synchronizedList是實現線程安全的列表的兩種方式。兩種實現方式分別針對不同情況有不同的性能表現,其中CopyOnWriteArrayList的寫操作性能較差,而多線程的讀操作性能較好。而Collections.synchronizedList的寫操作性能比CopyOnWriteArrayList在多線程操作的情況下要好很多,而讀操作因為是采用瞭synchronized關鍵字的方式,其讀操作性能並不如CopyOnWriteArrayList。因此在不同的應用場景下,應該選擇不同的多線程安全實現類。

4.4 為什麼Java的Vector類被認為是過時的或者廢棄的

Vector中對每一個獨立操作都實現瞭同步,這通常不是我們想要的做法。對單一操作實現同步通常不是線程安全的(舉個例子,比如你想遍歷一個Vector實例。你仍然需要申明一個鎖來防止其他線程在同一時刻修改這個Vector實例。如果不添加鎖的話

通常會在遍歷實例的這個線程中導致一個ConcurrentModificationException)同時這個操作也是十分慢的(在創建瞭一個鎖就已經足夠的前提下,為什麼還需要重復的創建鎖)

當然,即使你不需要同步,Vector也是有鎖的資源開銷的。

到此這篇關於Java中ArrayList和LinkedList區別的文章就介紹到這瞭,更多相關ArrayList和LinkedList區別內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: