Java集合系列(二):ArrayList、LinkedList、Vector的使用方法及區別


本篇博客主要講解List接口的三個實現類ArrayList、LinkedList、Vector的使用方法以及三者之間的區別。

注意:本文中代碼使用的JDK版本為1.8.0_191

1. ArrayList使用

ArrayList是List接口最常用的實現類,內部通過數組來實現,因此它的優點是適合隨機查找和遍歷,缺點是不適合插入和刪除。

ArrayList類的代碼聲明如下所示:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
}

1.1 添加元素

使用ArrayList添加元素有以下兩個重載:

boolean add(E e);
    
void add(int index, E element);

boolean add(E e);是將元素添加到集合的末尾,

void add(int index, E element);是將元素添加到指定的索引位置(索引是從0開始的)。

使用方法如下所示:

List<String> platformList = new ArrayList<>();

// 添加元素
platformList.add("博客園");
platformList.add("掘金");
platformList.add("微信公眾號");

// 添加重復元素,會添加成功,因為List支持添加重復元素
platformList.add("博客園");
platformList.add("掘金");


platformList.add(3, "個人博客");

1.2 獲取元素

獲取ArrayList中指定索引處的元素的使用方法如下所示:

System.out.println("索引為3的元素為:" + platformList.get(3));

如果指定的索引超出了集合的最大索引,比如platformList.get(6)就會拋出異常java.lang.IndexOutOfBoundsException

1.3 獲取集合元素個數

獲取ArrayList元素個數的使用方法如下所示:

System.out.println("platformList的元素個數為:" + platformList.size());

1.4 刪除元素

使用ArrayList刪除元素有以下兩個重載:

E remove(int index);

boolean remove(Object o);

E remove(int index);是刪除集合中指定索引處的元素,boolean remove(Object o);是刪除集合中的指定元素。

使用方法如下所示:

// 指定索引刪除重復的元素 "博客園" "掘金"
platformList.remove(4);
platformList.remove(4);
// 刪除指定元素 "個人博客"
platformList.remove("個人博客");

1.5 修改元素

修改ArrayList中指定索引處的元素值的使用方法如下所示:

platformList.set(0, "博客園:https://www.cnblogs.com/zwwhnly/");
platformList.set(1, "掘金:https://juejin.im/user/5c7ce730f265da2dca388167");
platformList.set(2, "微信公眾號:申城異鄉人");

1.6 判斷集合是否為空

判斷ArrayList是否為空的使用方法如下所示:

System.out.println("isEmpty:" + platformList.isEmpty());

1.7 遍歷元素(面試常問)

遍歷ArrayList的元素主要有以下3種方式:

  1. 迭代器遍歷
  2. for循環
  3. foreach循環

使用方法如下所示:

System.out.println("使用Iterator遍歷:");
Iterator<String> platformIterator = platformList.iterator();
while (platformIterator.hasNext()) {
    System.out.println(platformIterator.next());
}

System.out.println();
System.out.println("使用for循環遍歷:");
for (int i = 0; i < platformList.size(); i++) {
    System.out.println(platformList.get(i));
}

System.out.println();
System.out.println("使用foreach遍歷:");
for (String platform : platformList) {
    System.out.println(platform);
}

1.8 清空集合

清空ArrayList中所有元素的使用方法如下所示:

platformList.clear();

1.9 完整示例代碼

上面講解的幾點,完整代碼如下所示:

public static void main(String[] args) {
    List<String> platformList = new ArrayList<>();

    // 添加元素
    platformList.add("博客園");
    platformList.add("掘金");
    platformList.add("微信公眾號");

    // 添加重復元素,會添加成功,因為List支持添加重復元素
    platformList.add("博客園");
    platformList.add("掘金");


    platformList.add(3, "個人博客");

    System.out.println("索引為3的元素為:" + platformList.get(3));
    System.out.println("platformList的元素個數為:" + platformList.size());

    // 指定索引刪除重復的元素 "博客園" "掘金"
    platformList.remove(4);
    platformList.remove(4);
    // 刪除指定元素 "個人博客"
    platformList.remove("個人博客");

    System.out.println("platformList的元素個數為:" + platformList.size());

    platformList.set(0, "博客園:https://www.cnblogs.com/zwwhnly/");
    platformList.set(1, "掘金:https://juejin.im/user/5c7ce730f265da2dca388167");
    platformList.set(2, "微信公眾號:申城異鄉人");

    System.out.println("isEmpty:" + platformList.isEmpty());

    System.out.println("使用Iterator遍歷:");
    Iterator<String> platformIterator = platformList.iterator();
    while (platformIterator.hasNext()) {
        System.out.println(platformIterator.next());
    }

    System.out.println();
    System.out.println("使用for循環遍歷:");
    for (int i = 0; i < platformList.size(); i++) {
        System.out.println(platformList.get(i));
    }

    System.out.println();
    System.out.println("使用foreach遍歷:");
    for (String platform : platformList) {
        System.out.println(platform);
    }

    System.out.println();

    // 清空集合
    platformList.clear();
    System.out.println("isEmpty:" + platformList.isEmpty());
}

輸出結果為:

索引為3的元素為:個人博客

platformList的元素個數為:6

platformList的元素個數為:3

isEmpty:false

使用Iterator遍歷:

博客園:https://www.cnblogs.com/zwwhnly/

掘金:https://juejin.im/user/5c7ce730f265da2dca388167

微信公眾號:申城異鄉人

使用for循環遍歷:

博客園:https://www.cnblogs.com/zwwhnly/

掘金:https://juejin.im/user/5c7ce730f265da2dca388167

微信公眾號:申城異鄉人

使用foreach遍歷:

博客園:https://www.cnblogs.com/zwwhnly/

掘金:https://juejin.im/user/5c7ce730f265da2dca388167

微信公眾號:申城異鄉人

isEmpty:true

2. LinkedList使用

LinkedList也是List接口的實現類,內部使用鏈表結構存儲數據,因此它的優點是適合動態插入和刪除元素,缺點是隨機查找和遍歷速度比較慢。

LinkedList類的代碼聲明如下所示:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
}

LinkedList類的使用方法和ArrayList基本一樣,只需修改下聲明處的代碼即可:

List<String> platformList = new LinkedList<>();

3. Vector使用

Vector也是List接口的實現類,內部也是通過數組來實現。

Vector類的代碼聲明如下所示:

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
}

與ArrayList不同的是,Vector是線程安全的,即某一時刻只有一個線程能夠寫Vector,避免多線程同時寫而引起的不一致性。不過這也造成Vector的缺點:實現線程的同步需要額外的花費,因此它的訪問速度會比ArrayList慢一些。

可以認為Vector是ArrayList在多線程環境下的實現版本。

所以Vector類的使用方法和ArrayList基本一樣,只需修改下聲明處的代碼即可:

List<String> platformList = new Vector<>();

由於要支持線程同步,因此Vector類的很多方法都有synchronized關鍵字,如下所示:

public synchronized boolean isEmpty() {
    return elementCount == 0;
}

public synchronized int size() {
    return elementCount;
}

public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
}

4. ArrayList、LinkedList、Vector的區別(面試常問)

4.1 相同點

ArrayList、LinkedList、Vector都實現了List接口,所以使用方式很類似,通過上面的示例也能發現這一點。

4.2 不同點

但是ArrayList、LinkedList、Vector的內部實現方式不同,也就導致了它們之間是有區別的。

4.2.1 存儲結構

ArrayList和Vector是基於數組實現的,LinkedList是基於雙向鏈表實現的。

這也就導致ArrayList適合隨機查找和遍歷,而LinkedList適合動態插入和刪除元素。

關於數組和雙向鏈表,這里不做詳解,后續會單獨寫篇文章總結。

4.2.2 線程安全性

ArrayList和LinkedList是線程不安全的,Vector是線程安全的。

Vector可以看做是ArrayList在多線程環境下的另一種實現方式,這也導致了Vector的效率沒有ArraykList和LinkedList高。

如果要在並發環境下使用ArrayList或者LinkedList,可以調用Collections類的synchronizedList()方法:

Collections.synchronizedList(platformList);

4.2.3 擴容機制

ArrayList和Vector都是使用Object類型的數組來存儲數據的,ArrayList的默認容量是0,Vector的默認容量是10。

空說無憑,我們先看下ArrayList的使用示例:

List<String> strArrayList = new ArrayList<>();

for (int i = 0; i < 20; i++) {
    strArrayList.add(String.valueOf(i));
}

執行的ArrayList構造函數的源碼為:

transient Object[] elementData;

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

再看下Vector的使用示例:

List<String> strVector = new Vector<>();

for (int i = 0; i < 30; i++) {
    strVector.add(String.valueOf(i));
}

執行的Vector構造函數的源碼為:

protected Object[] elementData;
protected int capacityIncrement;

public Vector() {
    this(10);
}

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

public Vector(int initialCapacity, int capacityIncrement) {
      super();
      if (initialCapacity < 0)
          throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
      this.elementData = new Object[initialCapacity];
      this.capacityIncrement = capacityIncrement;
}

當向這兩種類型中添加元素時,若容量不夠,就會進行擴容,擴容的本質是產生一個新數組,將原數組的數據復制到新數組,再將新的元素添加到新數組中,使用的方法是Arrays.copyOf(),其中ArrayList擴容后的容量是之前的1.5倍,Vector默認情況下擴容后的容量是之前的2倍

仍然使用上面的ArrayList的例子:

List<String> strArrayList = new ArrayList<>();

for (int i = 0; i < 20; i++) {
    strArrayList.add(String.valueOf(i));
}

在執行完List<String> strArrayList = new ArrayList<>();后,此時strArrayList的容量是0,

然后在添加第1個元素時,strArrayList的容量會擴容為容量10,

當添加第11個元素時,strArrayList的容量會擴容為容量15,

當添加第16個元素時,strArrayList的容量會擴容為容量22,

如果還需要擴容,依次會擴容到33-->49。

看下ArrayList的源碼,就明白為什么會這樣擴容:

private static final int DEFAULT_CAPACITY = 10;

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
         return Math.max(DEFAULT_CAPACITY, minCapacity);
     }
     return 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);
}

最核心的代碼就是int newCapacity = oldCapacity + (oldCapacity >> 1);,所以ArrayList擴容后的容量是之前的1.5倍。

再看下上面的Vector例子:

List<String> strVector = new Vector<>();

for (int i = 0; i < 30; i++) {
    strVector.add(String.valueOf(i));
}

在執行完List<String> strVector = new Vector<>();后,此時strVector的容量是10,

當添加第11個元素時,strVector的容量會擴容為容量20,

當添加第21個元素時,strVector的容量會擴容為容量40,

如果還需要擴容,依次會擴容到80-->160。

看下Vector的源碼,就明白為什么會這樣擴容:

public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
}

private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
     // overflow-conscious code
     int oldCapacity = elementData.length;
     int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                      capacityIncrement : oldCapacity);
     if (newCapacity - minCapacity < 0)
         newCapacity = minCapacity;
     if (newCapacity - MAX_ARRAY_SIZE > 0)
         newCapacity = hugeCapacity(minCapacity);
     elementData = Arrays.copyOf(elementData, newCapacity);
}

最核心的代碼就是int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);,所以Vector默認情況下擴容后的容量是之前的2倍。

4.2.4 效率

ArrayList隨機查找和遍歷的效率會高一些,但動態插入和刪除元素的效率會低一些。

LinkedList動態插入和刪除元素的效率會高一些,但隨機查找和遍歷的效率會低一些。

如果需要在多線程下操作集合元素,建議使用Vector,否則的話,建議使用ArrayList。

5. 源碼及參考

ArrayList、LinkedList、Vector的區別和實現原理

Java深入 - 深入理解Java集合

Java進階(四十六)簡述ArrayList、Vector與LinkedList的異同點

原創不易,如果覺得文章能學到東西的話,歡迎點個贊、評個論、關個注,這是我堅持寫作的最大動力。

如果有興趣,歡迎添加我的微信:zwwhnly,等你來聊技術、職場、工作等話題(PS:我是一名奮斗在上海的程序員)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM