Java的Vector集合源碼深度解析以及應用介紹

  本文對Java中的Vector集合的源碼進行了深度解析,包括各種方法、特有的Enumeration迭代器機制,並且給出了Vector和ArrayList的區別以及使用建議。

1 Vector的概述

public class Vector
  extends AbstractList
  implements List, RandomAccess, Cloneable, Serializable

  Vector,來自於JDK1.0 的古老集合類,繼承自 AbstractList,實現了 List 接口 ,底層是數組結構,元素可重複,有序(存放順序),支持下標索引訪問,允許null元素。
  該類當中所有方法的實現都是同步的,方法採用了synchronized修飾,數據安全,效率低!可以看成ArrayList的同步版本,但是並不完全相同,比如迭代器。
  實現了 RandomAccess標誌性接口,這意味着這個集合支持 快速隨機訪問 策略,那麼使用傳統for循環的方式遍歷數據會優於用迭代器遍歷數據,即使用get(index)方法獲取數據相比於迭代器遍歷更加快速!
  還實現了Cloneable、Serializable兩個標誌性接口,所以Vector支持克隆、序列化。

2 Vector的源碼解析

  Vector的方法的原理和ArrayList非常相似,因此如果瞭解AarrayList那麼理解Vector將會很簡單!對於某些方法,本文在ArrayList集合中會有詳細介紹,因此強烈建議先學習ArrayList:Java的ArrayList集合源碼深度解析以及應用介紹

  Vector的底層數據結構就是一個數組,數組元素的類型爲Object類型,對Vector的所有操作底層都是基於數組的。
初始容量:
  調用空構造器時,立即初始化爲10個容量的數組,也可以指定初始容量。
加載因子:
  1,即存放數據時,如果存放數據後的容量大於底層數組的容量,那麼首先擴容。
擴容增量:
  新容量默認增加原容量的1倍,但是也可以在構造器指定擴容時的容量增量!如果新容量還是小於最小容量,則新容量還是等於最小容量!

2.1 主要類屬性

  相比於ArrayList,屬性還是很簡單的,多了一個capacityIncrement。

/**
 * 存放元素的底層容器,就是一個數組,當前數組的長度就是Vector的容量
 */
protected Object[] elementData;

/**
 * 元素的個數
 */
protected int elementCount;

/**
 * 當vector的大小大於其容量時,其容量擴充的大小,即容量增量。如果容量增量小於或等於零,則每次需要增長時vector的容量都會加倍。
 */
protected int capacityIncrement;

2.2 構造器與初始化容量

2.2.1 Vector()

  構造一個空集合,使其內部數據數組的大小初始化爲10,容量增量爲零。其源碼爲:

public Vector() {
    this(10);
}

  可以看到調用了另外一個構造方法。

2.2.2 Vector(int initialCapacity)

  構造具有指定初始容量並且其容量增量等於零的空集合。

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

  可以看到調用了另外一個構造方法。

2.2.3 Vector(int initialCapacity, int capacityIncrement)

  構造具有指定的初始容量和容量增量的空集合。

public Vector(int initialCapacity, int capacityIncrement) {
    //調用父類構造器
    super();
    //檢查初始化容量,如果小於0就拋出異常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                initialCapacity);
    //初始化指定容量的數組
    this.elementData = new Object[initialCapacity];
    //初始化容量增量
    this.capacityIncrement = capacityIncrement;
}

2.2.4 Vector(Collection<? extends E> c)

  構造一個包含指定 collection 中的元素的集合,這些元素按其 collection 的迭代器返回元素的順序排列。

public Vector(Collection<? extends E> c) {
    //獲取集合的元素數組,賦值給elementData
    elementData = c.toArray();
    //獲取此時集合的容量,賦值給elementCount
    elementCount = elementData.length;
    if (elementData.getClass() != Object[].class)
        //如果新加入的數組不是object[]類型的數組,則轉換爲object[]類型的數組
        elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

2.3 add方法與擴容機制

  add方法的原理基本和ArrayList的add方法一致,在ArrayList的文章中有詳細介紹,這裏不再贅述。Java的ArrayList集合源碼深度解析以及應用介紹

/**
 * 加了synchronized的同步方法
 * @param e 需要添加的元素
 * @return 添加成功,返回true
 */
public synchronized boolean add(E e) {
    //集合結構修改次數自增1
    modCount++;
    //確保數組容量夠用,最小容量爲當前元素個數+1
    ensureCapacityHelper(elementCount + 1);
    //添加元素
    elementData[elementCount++] = e;
    return true;
}


private void ensureCapacityHelper(int minCapacity) {
    //如果最小容量減去數組的長度的值大於0
    if (minCapacity - elementData.length > 0)
        //那麼有可能是需要擴容,或者數組長度移除
        grow(minCapacity);
}


private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


private void grow(int minCapacity) {
    //獲取老的容量
    int oldCapacity = elementData.length;
    //如果容量增量大於0,增新容量爲老容量加上容量增量,否則新容量是老容量的兩倍
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
            capacityIncrement : oldCapacity);
    //如果此時新容量減去老容量的值還是小於0,那麼新容量等於最小容量,或者數組長度溢出
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果此時新容量減去建議最大容量的值還是小於0,那麼新容量等於最小容量,或者數組長度溢出
    //此時需要拋出異常或者重新分配新容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        //拋出異常或者重新分配新容量
        newCapacity = hugeCapacity(minCapacity);
    //數組拷貝
    elementData = Arrays.copyOf(elementData, newCapacity);
}

/**
 * 和arraylist是同樣的邏輯
 * @param minCapacity
 * @return
 */
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}

2.4 addAll方法

public synchronized boolean addAll(Collection<? extends E> c) {
    //結構改變+1
    modCount++;
    //獲取加入集合的元素數組
    Object[] a = c.toArray();
    //獲取加入的元素的數量
    int numNew = a.length;
    //確保容量能夠容納這些元素
    ensureCapacityHelper(elementCount + numNew);
    //元素的拷貝存放
    System.arraycopy(a, 0, elementData, elementCount, numNew);
    //元素數量增加
    elementCount += numNew;
    //如果此集合由於調用而更改了結構,即numNew>0,則返回 true 
    return numNew != 0;
}

2.5 remove方法

public E remove(int index)

  移除此集合中指定索引位置上的元素,向左移動所有後續元素(將其索引減1),並返回此集合中移除的元素。
  從源碼中可以看到,需要調用System.arraycopy() 將刪除元素 index+1 後面的元素都複製到 index 位置上,該操作的時間複雜度爲 O(N),可以看出 ArrayList 刪除元素和擴容一樣,代價是非常高的。
  remove的源碼,還是比較簡單的:

public synchronized E remove(int index) {
    modCount++;
    //檢查要移除的元素索引是否越界(上界)
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    //獲取該索引處的元素
    E oldValue = elementData(index);
    //要移動的數據長度elementCount-(index + 1)  最小值0最大值size-1
    int numMoved = elementCount - index - 1;
    if (numMoved > 0)
        //將index+1後面的列表對象前移一位,該操作將會覆蓋index以及之後的元素,相當於刪除了一位元素
        System.arraycopy(elementData, index+1, elementData, index,
                numMoved);
    // 數組前移一位,size自減-,空出來的位置(原數組的有效數據的最後一位)置null,原來的具體的對象的銷燬由Junk收集器負責
    elementData[--elementCount] = null;
    //返回被移除的元素
    return oldValue;
}

2.6 get方法

public E get(int index)

  返回此集合中指定索引位置上的元素。

public synchronized E get(int index) {
    //檢查要獲取的元素索引是否越界(上界)
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    //返回索引處的元素
    return elementData(index);
}

2.7 set方法

public E set(int index,E element)

  用指定的元素替代此列表中指定索引位置上的元素。

public synchronized E set(int index, E element) {
    //檢查要設置的元素索引是否越界(上界)
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    //獲取舊的值
    E oldValue = elementData(index);
    //替換值
    elementData[index] = element;
    //返回舊值
    return oldValue;
}

2.8 clone方法

  返回的是一個全新的Vector實例對象,但是其elementData,也就是存儲數據的數組,存儲的對象還是指向了舊的Vector存儲的那些對象。也就是Vector這個類實現了深拷貝,但是對於存儲的對象還是淺拷貝。

public synchronized Object clone() {
    try {
        @SuppressWarnings("unchecked")
        //克隆集合對象
        Vector<E> v = (Vector<E>) super.clone();
        //克隆內部數組,導致雖然數組的引用不一樣,但是但是數組內部的相同索引處的元素引用指向同一個堆內存地址,即還是同一個對象
        v.elementData = Arrays.copyOf(elementData, elementCount);
        //設置新集合的modCount爲0
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

2.9 序列化

  同ArrayList一樣,Vector也有自己的writeObject方法,區別是Vector的內部數組被全部序列化存儲了,包括沒有使用道的部分,而ArrayList的內部數組沒有全部進行序列化,只是序列化了儲存了元素的部分。
  並且Vector沒有實現readObject方法,那麼將會按照默認的方式進行反序列化。
  writeObject:

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
    final java.io.ObjectOutputStream.PutField fields = s.putFields();
    final Object[] data;
    synchronized (this) {
        fields.put("capacityIncrement", capacityIncrement);
        fields.put("elementCount", elementCount);
        //直接對整個數組進行了序列化,相比於ArrayList代碼更加簡單,但是更佔用空間
        data = elementData.clone();
    }
    fields.put("elementData", data);
    s.writeFields();
}

3 迭代器

3.1 Enumeration迭代器的概述

  由於Vector屬於List接口集合體系,因此具有通用的iterator 和 listIterator迭代器(關於這兩個迭代器在ArrayList文章部分有詳細講解,這裏不再贅述)。但是我們知道List接口是JDK1.2的時候加進來的,但是Vector在JDK1.0的時候就出現了,因此Vector還具有自己獨有迭代器Enumeration,也被稱爲“枚舉”!
  Enumeration原本是一個接口,實現Enumeration接口的對象,又稱爲枚舉對象。通過它的api方法可以遍歷Vector集合的元素,但是該接口不屬於集合體系,並且JDK的API給出瞭如下建議:此接口的功能與Iterator接口的功能是重複的。此外,Iterator 接口添加了一個可選的移除操作,並使用較短的方法名。新的實現應該優先考慮使用 Iterator 接口而不是 Enumeration 接口。
  由於Enumeration迭代器比較古老,因此功能也很簡略,並沒有iterator 和 listIterator迭代器的快速失敗機制,因此可能出現一些不會拋出異常的“異常情況”,比如下面的代碼:

/**
 * Enumeration迭代器的死循環
 */
@Test
public void test2() {
    Vector<Integer> vector = new Vector<>(Collections.singletonList(1));
    //獲取自己的迭代器Enumeration
    Enumeration elements = vector.elements();
    int j = 0;
    //是否存在更多元素
    while (elements.hasMoreElements()) {
        //內部採用集合的方法添加元素,如果是iterator 和 listIterator 迭代器,那麼會馬上拋出ConcurrentModificationException異常
        //但是由於Enumeration迭代器,沒有這個功能,因此會導致死循環,直到OOM
        vector.add(j++);
        //獲取下一個元素
        Object o = elements.nextElement();
        //打印元素
        System.out.println(o);
    }
}

  上面的代碼將會造成死循環!

3.2 Enumeration迭代器的實現

public Enumeration elements()

  返回此集合的枚舉。返回的 Enumeration 對象將具有此集合中的所有元素。第一項爲索引0處的元素,然後是索引1處的元素,依此類推。
  下面是源碼:

public Enumeration<E> elements() {
    //和iterator 與 listIterator迭代器不一樣
    //Enumeration迭代器甚至簡陋得"沒有自己的內部類實現"
    //這裏返回的就是一個匿名內部類,即返回一次實現一次。
    return new Enumeration<E>() {
        //用於遍歷元素個數的計數
        int count = 0;

        //是否還存在元素  通過比較count和elementCount的值
        //如果遍歷的元素個數小於外部集合的元素個數,那就說明有元素,可以獲取,返回true
        public boolean hasMoreElements() {
            return count < elementCount;
        }

        //返回下一個元素
        public E nextElement() {
            synchronized (Vector.this) {
                //如果遍歷的元素個數小於外部集合的元素個數,那就說明有元素,可以獲取
                if (count < elementCount) {
                    //返回該遍歷的元素個數索引處的元素,同時遍歷的元素個數自增1
                    return elementData(count++);
                }
            }
            throw new NoSuchElementException("Vector Enumeration");
        }
    };
}

  我們看到,Enumeration的實現非常簡單,返回的是一個匿名內部類,並沒有“併發修改”的檢測,並且內部只提供了兩個方法hasMoreElements()和nextElement()方法。並沒有add、remove等修改集合元素方法,功能更加簡陋!

boolean hasMoreElements()

  當且僅當此枚舉對象至少還包含一個可提供的元素時,才返回 true;否則返回 false。

E nextElement()

  如果此枚舉對象至少還有一個可提供的元素,則返回此枚舉的下一個元素。

3.3 分析Enumeration迭代器的死循環

  我們來分析上面的案例是如何導致死循環的!
  首先獲取迭代器,該迭代器和外部集合共用一個內部數組。
  進入循環,hasMoreElements()判斷是否存在下一個元素,由於原集合存在一個元素,因此count=0 < elementCount=1,即返回true。
  然後循環體內部,首先對外部集合添加了元素,這會導致elementCount++,變成2。繼續nextElement()方法,同樣判斷存在元素,因此返回elementData(0++),即返回elementData[0],然後count自增一變成1,第一次循環結束。
  第二次循環,這是hasMoreElements()發現count=1 < elementCount=2,即返回true。
然後循環體內部,又首先對外部集合添加了元素,這會導致elementCount++,變成3。繼續nextElement()方法,同樣判斷存在元素,因此返回elementData(1++),即返回elementData[1],然後count自增一變成2,第二次循環結束。
  我們發現,無論怎麼循環,count始終小於elementCount,這就是導致死循環的原因,在開發過程如果要使用Vector集合,那麼要避免這種情況,並且最好是不去使用老舊的Enumeration迭代器!

3.4 使用枚舉遍歷ArrayList?

/**
 1. 使用枚舉遍歷ArrayList
 */
@Test
public void test3() {
    ArrayList<String> al = new ArrayList<>(Arrays.asList("b", "a", "s", "c", "11", null));
    Vector<String> v = new Vector<>(al);
    Enumeration<String> elements = v.elements();
    while (elements.hasMoreElements()) {
        String s = elements.nextElement();
        System.out.println(s);
    }
}

4 Vector的總結和使用

Vector和ArrayList的異同點:
相同點:

  1. 底層是數組結構,元素可重複,有序(存儲順序),支持下標索引訪問,允許null元素。

不同點:

  1. Vector:在JDK1.0固有的集合類。該類當中所有方法的實現都是:線程同步,數據安全,效率低。在JDK1.2被加入到集合框架當中。
  2. ArrayList:在JDK1.2新添加的集合類。該類當中所有的方法的實現都是:線程異步,數據不安全,效率高。
  3. 擴容:Vector每次擴容變成原來的2倍(也可以通過構造函數設置容量增量),而 ArrayList 是 每次擴容變成原來的1.5 倍左右。當然對於它們兩個都是:如果計算出的擴容後的容量還是小於最小容量,那麼擴容後的容量就是最小容量。

Vector使用建議:
  Vector集合的方法都加了snchronized,因此效率較低,如果想要使用線程安全的List集合,那麼可以使用Collections.synchronizedList()得到一個線程安全的List,實際上它內部使用snchronized關鍵字修飾的代碼塊,效率也很一般,推薦使用JUC包下的 CopyOnWriteArrayList類,效率比較高,但是它的“寫時複製”機制可能會造成數據不同步。總之,面試中,Vector被問到的概率比較小,而在開發過程中,不推薦使用Vector集合
  我們後續將會介紹的更多集合,比如LinkedList、TreeMap、HashMap,LinkedHashMap等基本集合以及JUC包中的高級併發集合。如果想學習集合源碼的關注我的更新!

如果有什麼不懂或者需要交流,可以留言。另外希望點贊、收藏、關注,我將不間斷更新各種Java學習博客!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章