Java容器之Vector源碼分析(Vector容器爲啥線程安全呢?)

  在上一篇博客 Java容器之ArrayList源碼分析(這應該是Java中最簡單的容器吧) 從源碼的角度分析了ArrayList容器,現在我們看下Vector容器又是什麼。

註明:以下源碼分析都是基於jdk 1.8.0_221版本
在這裏插入圖片描述

一、Vector容器概述

  Vector類的聲明如下:

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

在這裏插入圖片描述
  相比於Vector容器,大部分的小夥伴用的ArrayList容器相對較多一點。既然Java已經設計了ArrayList容器,爲啥要又引入Vector容器,難道又是這些大佬太閒了?
  看過我之前分析ArrayList容器的源碼或者懂一點的肯定知道,ArrayList容器沒有涉及任何鎖相關的內容,也就是說ArrayList容器不支持併發讀寫,而這正是Vector優勢所在。(不要說,爲了併發單獨設計出一個容器,有點小題大做。而是因爲鎖機制的引入會降低容器的讀寫能,在每次讀寫前都要上鎖、釋放鎖,增加了繁文縟節。)
  與ArrayList容器一樣,Vector容器的實現原理同樣是封裝了一個數組,並且數組的擴容、縮小都由容器自動控制。
在這裏插入圖片描述

二、Vector類中主要屬性

  Vector類中主要屬性如下:

/**
 * 容器底層封裝的存放數據的數組
 */
protected Object[] elementData;

/**
 * 容器中包含的元素數量(注意與elementData數組長度的區別)
 */
protected int elementCount;

/**
 * 當容器的大小大於其容量時,capacityIncrement爲容器自動遞增的量。
 * 如果容量增量小於或等於零,則向量的容量將在每次需要增長時加倍。
 */
protected int capacityIncrement;

三、Vector類的構造器

  Vector類一共有4個構造器,分別如下:

/**
 * 指定容器初始化的容量、每次增長的量
 */
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

/**
 * 指定容器初始化的大小,擴容增長的量設置爲0,表示容器每次擴大爲原來的2倍
 */
public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

/**
 * 默認容器初始化的大小爲10
 */
public Vector() {
    this(10);
}

/**
 * 複製構造器,將容器c中的元素copy到當前初始化的容器
 */
public Vector(Collection<? extends E> c) {
    elementData = c.toArray();
    elementCount = elementData.length;
    // c.toArray()返回可能不是Object[].class的數組,需要轉換一下
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

四、查找相關方法

1、get方法

/**
 * 通過index獲取容器中的元素(此方法用雖不會修改容器,不過還是用synchronized修飾了,同步方法,this對象鎖,防止讀到不是最新的數據)
 */
public synchronized E get(int index) {
	// 檢查是否超出最後一個元素的邊界(注意這裏的邊界檢查並不是檢查下標是否超過數組的長度)
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
	// 下標合法則調用elementData方法獲取元素
    return elementData(index);
}

@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

2、contains方法

/**
 * 查找容器是否包含該對象
 */
public boolean contains(Object o) {
	// 此方法雖沒有加鎖,但是在indexOf方法中加鎖了
    return indexOf(o, 0) >= 0;
}

3、indexOf方法

/**
 * 查找對象在容器中的第一個下標
 */
public int indexOf(Object o) {
	// 此方法雖沒有加鎖,但是在indexOf(o, 0)方法中加鎖了
    return indexOf(o, 0);
}

/**
 * 從[index, elementCount)搜索對象的第一個下標(此方法用synchronized修飾,this對象鎖)
 */
public synchronized int indexOf(Object o, int index) {
	// 如果o是null,則不能調用equals方法,所以得分情況寫
    if (o == null) {
        for (int i = index ; i < elementCount ; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = index ; i < elementCount ; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    // 沒找就返回 -1
    return -1;
}

  還有一個lastIndexOf方法,與indexOf方法的作用類似,只不過是從後往前查找,找到的下標是最後一次出現的下標。

五、插入相關方法

1、add方法

/**
 * 往容器尾端插入元素(修改操作,肯定要上鎖,使用synchronized修飾,this對象鎖
 */
public synchronized boolean add(E e) {
	// 插入元素屬於結構性調整(該變量在父類AbstractList中有定義)
    modCount++;
    // 檢查容器是否還有空間(沒有就要擴容
    ensureCapacityHelper(elementCount + 1);
    // 插入尾端即可
    elementData[elementCount++] = e;
    return true;
}

/**
 * 將元素插入到指定下標位置
 */
public void add(int index, E element) {
	// 此方法雖沒有上鎖,但是insertElementAt方法上鎖了
    insertElementAt(element, index);
}
/**
 * 將元素插入到指定下標位置
 */
public synchronized void insertElementAt(E obj, int index) {
	// 插入元素屬於結構性調整(該變量在父類AbstractList中有定義)
    modCount++;
    // 插入的下標必須在[0, elementCount)中
    if (index > elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount);
    }
    // 檢查容器是否還有空間(沒有就要擴容
    ensureCapacityHelper(elementCount + 1);
    // arraycopy的五個參數分別是,數組src,起始下標,數組des,起始下標,複製的個數
    // 此時我們需要把elementData[index]這個位置空出來,也就是[index, elementCount)區間的元素全部往後移動一個位置,index移動到index+1,index+1移動到index+2
    System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
    elementData[index] = obj;
    elementCount++;
}

2、set方法

/**
 * 修改容器對應下標中的元素(此方法同樣加鎖
 */
public synchronized E set(int index, E element) {
	// 修改的下標必須在[0, index)區間內
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

六、刪除相關方法

1、remove方法

/**
 * 刪除容器的指定對象
 */
public boolean remove(Object o) {
    return removeElement(o);
}

/**
 * 刪除容器的指定對象(只會刪除第一個,如果容器中包含多個),使用synchronized修飾,this對象鎖
 */
public synchronized boolean removeElement(Object obj) {
	// 刪除元素屬於結構性調整(該變量在父類AbstractList中有定義)
    modCount++;
    // 獲取obj的第一個下標
    int i = indexOf(obj);
    if (i >= 0) {
    	// 查找到了則進行刪除
        removeElementAt(i);
        return true;
    }
    return false;
}

/**
 * 通過下標移除元素
 */
public synchronized E remove(int index) {
	// 刪除元素屬於結構性調整(該變量在父類AbstractList中有定義)
    modCount++;
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    E oldValue = elementData(index);
	// [index + 1, elementCount)區間的中的元素全部前一個位置
    int numMoved = elementCount - index - 1;
    if (numMoved > 0)
    	// arraycopy的五個參數分別是,數組src,起始下標,數組des,起始下標,複製的個數
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--elementCount] = null; // Let gc do its work

    return oldValue;
}

七、其它方法

1、trimToSize方法

/**
 * 此方法是去除容器尾端的空閒位置
 */
public synchronized void trimToSize() {
    modCount++;
    int oldCapacity = elementData.length;
    if (elementCount < oldCapacity) {
    	// 只要把容器中的元素複製到一個大小剛好爲elementCount的數組即可
        elementData = Arrays.copyOf(elementData, elementCount);
    }
}

2、grow擴容方法

/**
 * 此方法雖沒有上鎖,但是調用此方法都是在插入無位置的時候,而插入的方法都加了鎖
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 前面在介紹Vector類屬性時,說過capacityIncrement是每次擴大的增長量,如果爲0,則每次擴大爲原來的2倍
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
    // 新的容量不能太小(小於minCapacity),也不能太大(大於Integet.MAX_VALUE
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 複製到一個長度爲newCapacity的新數組中即可
    elementData = Arrays.copyOf(elementData, newCapacity);
}

八、總結

  可以看出VectorArrayList容器的實現原理是一毛一樣的,都是封裝了一個數組,並且把數組的擴容、縮小交給容器自己管理。不過Vector支持多線程併發訪問,因爲修改容器的方法都加上了synchronized關鍵字修飾(this對象鎖,鎖住整個容器對象)。由於底層是數組,所以隨機訪問(通過下標訪問)效率高,但是在刪除節點、插入節點時比較麻煩,刪除節點時需要進行大量元素的前移,插入節點時需要進行大量元素的後移,數組容量有限,還需要進行擴容(整個容器中的元素都要複製一遍),效率比較低。
  總的來說就是VectorArrayList容器的查詢效率高,但是刪除、插入效率低。下一篇博文將分析LinkedList容器,可以看看它與這兩個容器有什麼不同。

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