java集合框架---ArrayList/Vector/LinkedList詳解

ArrayList簡介

ArrayList是list接口的可變數組的實現。與通常數組不一樣的是,它的容量能夠動態增加。html

ArrayList繼承了AbstractList抽象類,實現了List,RandomAccess, Cloneable, java.io.Serializable接口,根據實現的接口看,它支持隨機訪問,支持克隆,支持序列化。java

Arraylist的特色:1)保證元素按照規定的順序排列--元素的輸出順序和輸入順序一致;2)可快速隨機訪問;3)元素能夠爲nullnode

插播一下

RandomAccess接口是一個標記接口(marker),它沒有任何方法,被list的子類使用,若是list的子類實現了此接口,則表明它能夠快速隨機訪問存儲的元素.算法

他的意義在於當要實現某些算法時,會判斷當前類是否實現了RandomAccess接口,會根據結果選擇不一樣的算法.例如:數組

public static void shuffle(List<?> var0, Random var1) { int var2 = var0.size(); if (var2 >= 5 && !(var0 instanceof RandomAccess)) { Object[] var6 = var0.toArray(); for(int var4 = var2; var4 > 1; --var4) { swap(var6, var4 - 1, var1.nextInt(var4)); } ListIterator var7 = var0.listIterator(); for(int var5 = 0; var5 < var6.length; ++var5) { var7.next(); var7.set(var6[var5]); } } else { for(int var3 = var2; var3 > 1; --var3) { swap(var0, var3 - 1, var1.nextInt(var3)); } } }
View Code

jdk建議實現了RandomAccess接口的list遍歷時使用loop去遍歷,並未實現(例如LinkedList)的使用迭代器去遍歷(由於效率緣由,在最後我會測試完貼代碼上來的)。安全

RandomAccess源碼的註釋裏面都寫了,啦啦啦:併發

/* * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */

package java.util; /** * Marker interface used by <tt>List</tt> implementations to indicate that * they support fast (generally constant time) random access. The primary * purpose of this interface is to allow generic algorithms to alter their * behavior to provide good performance when applied to either random or * sequential access lists. * * <p>The best algorithms for manipulating random access lists (such as * <tt>ArrayList</tt>) can produce quadratic behavior when applied to * sequential access lists (such as <tt>LinkedList</tt>). Generic list * algorithms are encouraged to check whether the given list is an * <tt>instanceof</tt> this interface before applying an algorithm that would * provide poor performance if it were applied to a sequential access list, * and to alter their behavior if necessary to guarantee acceptable * performance. * * <p>It is recognized that the distinction between random and sequential * access is often fuzzy. For example, some <tt>List</tt> implementations * provide asymptotically linear access times if they get huge, but constant * access times in practice. Such a <tt>List</tt> implementation * should generally implement this interface. As a rule of thumb, a * <tt>List</tt> implementation should implement this interface if, * for typical instances of the class, this loop: * <pre> * for (int i=0, n=list.size(); i &lt; n; i++) * list.get(i); * </pre> * runs faster than this loop: * <pre> * for (Iterator i=list.iterator(); i.hasNext(); ) * i.next(); * </pre> * * <p>This interface is a member of the * <a href="{@docRoot}/../technotes/guides/collections/index.html"> * Java Collections Framework</a>. * * @since 1.4 */
public interface RandomAccess { }
View Code

每一個ArrayList實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。它老是至少等於列表的大小。隨着向ArrayList中不斷添加元素,其容量也自動增加。自動增加會帶來數據向新數組的從新拷貝,所以,若是可預知數據量的多少,可在構造ArrayList時指定其容量。可是擴容這個操做不是同步的,因此不要在併發狀況下使用ArrayList啊,若是多個線程都在改它的結構(數量發生變化,一會多了,一會少了)的話,那麼恭喜你,你的代碼要崩掉了。併發狀況下能夠用vector,CopyOnWriteArrayList(建議使用),或者你就想用ArrayList,這樣也能夠的:app

List list = Collections.synchronizedList(new ArrayList(...));

ArrayList源碼解析(jdk版本1.8.0_144)

1)幾個比較不對很重要的初始化的值

private static final int DEFAULT_CAPACITY = 10;//默認的大小

    private static final Object[] EMPTY_ELEMENTDATA = {};//空數組


    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA {}; //沒有給定size的時候,構造方法就默認給這個啦,默認大小是10


    transient Object[] elementData; // 數據就是在這個數組裏面村的

    private int size;//list的大小啦
View Code

其中最最重要的兩個對象:dom

 elementData 是"Object[] 類型的數組",它保存了添加到ArrayList中的元素。實際上,elementData是個動態數組,咱們能經過構造函數 ArrayList(int initialCapacity)來執行它的初始容量爲initialCapacity;若是經過不含參數的構造函數ArrayList()來建立 ArrayList,則elementData的容量默認是10。elementData數組的大小會根據ArrayList容量的增加而動態的增加。ide

size 則是動態數組的實際大小

2)構造方法

ArrayList提供了三種構造器:

public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * Constructs an empty list with an initial capacity of ten. */
    public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */
    public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA; } }

1)本身指定大小

2)不指定大小,默認給一個,默認值爲10(第一次添加元素的時候,設置的size爲默認大小10)

3)構造一個指定初始容量的空列表以及構造一個包含指定collection的元素的列表

 

 
  
 
 

ArrayList時怎麼構造一個默認初始容量爲10的空列表:

1) 初始狀況:elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; size = 0;

2) 當向數組中添加第一個元素時,經過add(E e)方法中調用的ensureCapacityInternal(size + 1)方法,即ensureCapacityInternal(1);

3) 在ensureCapacityInternal(int minCapacity)方法中,可得的minCapacity=DEFAULT_CAPACITY=10,而後再調用ensureExplicitCapacity(minCapacity)方法,即ensureExplicitCapacity(10);

4) 在ensureExplicitCapacity(minCapacity)方法中調用grow(minCapacity)方法,即grow(10),此處爲真正具體的數組擴容的算法,在此方法中,經過elementData = Arrays.copyOf(elementData, 10)設置了默認的初始大小10.

因此若是你沒有定義arrayList的大小的話,他在第一次添加元素的時候,調用了給list調整大小的方法grow(),給list設置了默認大小10.具體仍是不太明白爲啥的,再本地debug仔細看下最好了,我也是debug看的。

3)擴展容量

咱們常常說數據和arrayList有啥區別,會說數組的容量時固定的,不可變的;arrayList能夠動態調整大小 。

那arrayList時如何調整大小呢?

請看源碼:

public boolean add(E e) { ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code
        if (minCapacity - elementData.length > 0) grow(minCapacity); } 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); }

用add()方法舉個栗子哈,addAll()同理。

從以上源碼能夠看出,每當向數組中添加元素時,都要去檢查添加元素後的個數是否會超出當前數組的長度,若是超出,數組將會進行擴容,以知足添加數據的需求。數組擴容實質上是經過私有的方法ensureCapacityInternal(int minCapacity) -> ensureExplicitCapacity(int minCapacity) -> grow(int minCapacity)來實現的。看代碼也能夠看出來,擴容了1.5倍啦。

Vector

Vector就很少說了,由於它其實就跟arrayList差很少啦,就時一個同步的arrayList,能夠再併發狀況下使用,來看看源碼:

public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }

用add()方法舉個栗子,其實不少方法都加入了synchronized同步語句,來保證線程安全。可是我不多見到有人使用vector的,由於再併發下,效率確實不怎麼高。若是有併發的需求,能夠用以前提到CopyOnWriteArrayList。

LinkedList簡介

LinkedList基於鏈表(雙端鏈表)實現,刪除和插入操做速度快於arrayList,但也是由於基於鏈表,因此不支持隨機訪問,查詢速度比arrayList慢。

LinkedList繼承AbstractSequentialList類,實現了List,Deque,Cloneable, java.io.Seriaizable接口。

LinkedList源碼解析(jdk版本1.8.0_144)

transient int size = 0; transient Node<E> first; transient Node<E> last;

 

 size表明的時鏈表的長度,也就是list的大小了;兩個變量,first指向鏈表頭部,last指向鏈表尾部。

再看一下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; } }

很明顯能夠看出來這是一個雙端鏈表了,分別保存了對上一個節點及下一個節點的引用。

再看幾個經常使用的方法:

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);//以當前的尾部節點爲新節點的pre,建立新節點。
        last = newNode;//將鏈表尾部指向新節點
        if (l == null)//若是鏈表爲空,那麼新節點是頭節點也是尾節點
            first = newNode; else//鏈表不爲空,那麼將該結點做爲原鏈表尾部的next
            l.next = newNode; size++; modCount++; }

 

2,在指定位置添加元素

public void add(int index, E element) { checkPositionIndex(index);//檢查index時否合法

        if (index == size)//若是index等於size,那就添加到尾部
 linkLast(element); else linkBefore(element, node(index));//不然就往中間插入啦
 } 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); //若是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; } }

 

3,獲取指定索引的數據

public E get(int index) { checkElementIndex(index);//檢查索引是否合法
        return node(index).item;//查找數據
    }

這就是爲啥說linkedList查找數據慢了,它是用鏈表實現的,不像數組有索引標識,因此固然慢啊。

好了就這麼多吧,舉幾個栗子就行,我這一篇都寫了很久了,快懶死我了,寫完我好寫別的啊。

我不是大神啊,若是有人不當心看到個人文章,以爲我說得不對,你們就提出來啊,別罵我啊。