集合框架——ArrayList與LinkedList、Vector的區別

目錄

一、相同之處

二、不同之處

1、ArrayList與LinkedList區別

2、ArrayList與Vector區別

三、使用場景


List接口是Collection接口下的子接口List中的元素是有序的,可以重複的,List接口的主要實現類有三個:ArrayList、LinkedList和Vector。

一、相同之處

  • 都是List接口的實現類,因此他們具有List列表的共同特性,他們是有序的,可以容納重複的元素,允許元素爲null。
  • 都可以使用Collection下的Iterator接口的iterator()方法和Iterator接口的子接口ListIterator的listIterator()方法,並且Iterator和ListIterator迭代器都具有fail-fast機制。

二、不同之處

1、ArrayList與LinkedList區別

(1)底層數據結構不同,ArrayList和Vector都是基於動態數組實現的,LinkedList是基於雙向鏈表實現的。

ArrayList是由Object類型的數組實現的,如果傳入初始容量參數,則創建一個initialCapacity大小的數組,如果沒有傳入initialCapacity,那麼創建一個默認大小(10)的數組。

(2)實現接口不同,除了都有的Serializable、Cloneable、Iterable、Collection和List,ArrayList還實現了RadomAccess接口,LinkedList還實現了Deque接口

(3)不同操作效率不同,對於隨機訪問get和set效率不同,ArrayList優於LinkedList,因爲LinkedList要移動指針遍歷得到待查找的node。對於新增和刪除操作add和remove效率不同,LinedList比較佔優勢,因爲ArrayList要移動數據。 

List的操作效率
  get和set add remove
ArrayList O(I) O(n) O(n)
LinkedList O(n) O(I) O(n)

這一點要看實際情況的。若只對單條數據插入或刪除,ArrayList的速度反而優於LinkedList。但若是批量隨機的插入刪除數據,LinkedList的速度大大優於ArrayList. 因爲ArrayList每插入一條數據,要移動插入點及之後的所有數據。

ArrayList的get

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
    }

E elementData(int index) {
    return (E) elementData[index];
    //elementData是用於存儲元素的動態數組
    }

可以看出ArrayList的get就是從數組進行讀取,效率爲O(1)。

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)) {  //判斷index是否超過size的一般,如果不是則從head開始遍歷
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {       //否則從tail開始遍歷
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

可以看出,雖然LinkedList實現了List接口,因此必須實現get(index)方法,但是get()方法是使用迭代器遍歷來實現的,因此效率爲O(n)。 

ArrayList的add和remove:通過System.arraycopy來實現

add操作:

public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

remove操作:

public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }

可以看出ArrayList的add和remove操作都是通過System.arraycopy來實現的,需要移動index後每一個元素,操作效率爲O(n)。

LinkedList的add和remove:通過link和unlink來實現

add操作:

public void add(int index, E element) {
        checkPositionIndex(index);  //查看index是否在鏈表範圍內

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

public void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);   //Node(NOde <E> pre, E element, Node<E> next)
        last = newNode;
        if (l == null)  //如果鏈表還是空鏈表,那麼把當前節點設爲頭節點
            first = newNode;
        else
            l.next = newNode;   //否則把當前節點添加到last後面
        size++;
        modCount++;  //用於fail-fast機制
    }
 
public void linkBefore(E e, Node<E> succ) {  //把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)  //如果succ是頭結點,那麼把當前節點設置爲頭結點
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

可以看出,由於add操作只需要修改雙向鏈表的next和prev指針的地址,因此操作效率爲O(1)。

remove操作:

//刪除index下標位置的元素
public E remove(int index) {
        checkElementIndex(index); //檢查index是否在鏈表範圍內
        return unlink(node(index));
    }

//刪除元素o,因爲LinkedList中的元素是可以重複且爲null的,所以要遍歷整個表,把所有o元素都刪掉
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;
                }
            }
        }
 
public 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++;   //用於fail-fast機制
        return element;
    }

可以看出,雖然unlink操作的效率是O(1),但是不論是索引(index)刪除還是元素(object)刪除都需要遍歷最多半個鏈表(索引刪除調用node方法進行了遍歷查找)才能找到要刪除的node,因此效率爲O(n)

(4)佔用內存不同,LinkedList 比 ArrayList 需要更多的內存,因爲鏈表除了數據本身還需要存儲next指針指向下一個node的地址。

 

 

2、ArrayList與Vector區別

(1)同步性不同,LinkedList和ArrayList是不同步的,Vector是同步的(使用synchronized進行強同步)。

(2)擴容方法不同,LinkedList基於鏈表實現,容量是無限的;ArrayList和Vector是基於數組實現的,容量是有限的,在需要的時候要擴容,但是ArrayList和Vector的擴容方式稍微不同,ArrayList是newCapacity=oldCapacity*3/2-1,而Vector是與“增長係數有關”,若指定了“增長係數”,且“增長係數有效(即大於0)”;那麼,每次容量不足時,“新的容量”=“原始容量+增長係數”。若增長係數無效(即,小於/等於0),則“新的容量”=“原始容量 x 2”。

 

ArrayList的動態數組的擴容

因爲數組的大小是固定的,而ArrayList的大小是動態可調的,那麼他是如何實現的呢?首先,由ensureCapacity()方法確認ArrayList容量,然後由grow()方法擴容。

 //ensureCapacity()方法用於確認當前的elementData[]數組是否能容納minCapacity的元素
public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
//ensureExplicitCapacity()方法用於擴容,也就是說調用這個函數說明明確的要擴容,擴容對數組的結構進行了修改,因此modcount加1
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
//數組的擴容:
//首先計算需要的容量,newCapacity = oldCapacity + (oldCapacity >> 1),說明newCapacity是oldCapacity的大概3/2,如果
//確切一點,就是newCapacity = 3/2*oldCapacity-1;
//然後,如果newCapacity<minCapacity,那麼newCapacity設爲minCapacity
//最後調用Array.copyOf()方法把舊數組複製到新數組中
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

這裏我們來看看Array.copyOf( )是怎麼操作的:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

可以看到Array.copyOf( )方法中,是新建了一個數組,然後把就數組拷貝在新數組中,此外,如果新數組和舊數組的類型不同,還需要把舊類型輕質轉換爲新類型。那麼可以看出,ArrayList的擴容是通過新建一個數組來實現的。

 

三、使用場景

如果涉及到“隊列”、“棧”和“鏈表”等場景,那麼可以使用List下的實現類,具體要根據需求和各個實現類的特點選擇。

  • 如果需要快速隨機訪問,那麼使用ArrayList
  • 如果需要頻繁的插入和刪除中間節點,那麼使用LinkedList
  • 如果需要在多線程中同步,那麼使用Vector,但是由於歷史原因Vector基本已經被棄用,可以用java.util.Concurrent包下的concurrentLinkedDeque或者concurrentQueue來代替Vector
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章