Vector源碼分析拇厢,一個古老的List實現(xiàn)類

前言

對于一個Java開發(fā)來說,集合是無時無刻不存在的一個工具類晒喷,我們經(jīng)常使用集合存儲同一類型的數(shù)據(jù)旺嬉,當(dāng)然也可以存儲不同類型的數(shù)據(jù),具體得看集合的范型是什么厨埋。集合的總類有多種邪媳,今天我們來聊一聊List集合。
List是一個集合的接口荡陷,實現(xiàn)List接口的實現(xiàn)類都有一個特性:有序雨效,可重復(fù)。常見的實現(xiàn)類有Vector废赞、ArrayList和LinkedList徽龟,接下來我們一起討論一下他們的區(qū)別,為什么一個List接口會有這么多實現(xiàn)唉地,他們分別適用于什么場景据悔。

Vector

這是一個元老級的集合實現(xiàn) Since JDK1.0 也就是JDK誕生的時候就存在了。它相比于其他兩個實現(xiàn)類最大的區(qū)別就是Vector可以保證原子性耘沼,能保證線程安全极颓,成也蕭何拜也蕭何,正是因為它的原子性導(dǎo)致它的性能低下群嗤,這也是我們工作中不考慮使用的原因菠隆。

源碼分析

它有三個重要的屬性

/*
 * 數(shù)據(jù)存儲的地方,沒錯,就是用一個數(shù)組存儲
 */
 protected Object[] elementData;
 /*
  * 當(dāng)前容器中數(shù)據(jù)的數(shù)量骇径,也就是elementData數(shù)組存儲了多少數(shù)據(jù)
  */
 protected int elementCount;
/*
 * 當(dāng)數(shù)組容量不足時躯肌,擴容的大小,可通過構(gòu)造方法指定破衔,不指定默認(rèn)擴大兩倍
 */
 protected int capacityIncrement;

整個容器方法的實現(xiàn)基本都是圍繞這三個參數(shù)來實現(xiàn)清女。
當(dāng)我們使用一個類時,需要通過構(gòu)造方法進行實例化晰筛,Vector提供的構(gòu)造方法如下

/*
 * 無參構(gòu)造方法
 */
public Vector() {
    this(10);
}
/*
 * 指定初始容量大小
 */
public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}
/*
 * 以上兩個構(gòu)造方法最終都是調(diào)用這個構(gòu)造方法實例化嫡丙,
 * @param initialCapacity 初始容量大小,也就是指定數(shù)組長度传惠,比需大于0
 * @param capacityIncrement 擴容增長數(shù)量
 */
public Vector(int initialCapacity, int capacityIncrement) {
      super();
      if (initialCapacity < 0)
          throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
      this.elementData = new Object[initialCapacity];
      this.capacityIncrement = capacityIncrement;
}

以上構(gòu)造方法我們可以根據(jù)實際情況而定迄沫,如果我們能知道存儲數(shù)據(jù)的量,我們可以通過第二個構(gòu)造方法指定容量大小卦方,減少擴容帶來的性能問題羊瘩。
執(zhí)行構(gòu)造方法后,我們的容器就創(chuàng)建好了盼砍,接下來我們就可以往容器里放數(shù)據(jù)尘吗。

add(E e)
add方法是容器為我們提供添加數(shù)據(jù)的方法,老規(guī)矩浇坐,先看源碼

public synchronized boolean add(E e) {
      modCount++;
      // 擴容相關(guān)
      ensureCapacityHelper(elementCount + 1);
      // 插入數(shù)據(jù)
      elementData[elementCount++] = e;
      return true;
}
/*
 *  為什么這個方法沒有使用synchronized修飾呢睬捶?
 *    因為調(diào)用該方法的所有方法都使用了synchronized修飾,外部已經(jīng)保證原子* 性近刘,無需多此一舉
 */
private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
      // 所需容量 - 數(shù)組長度
      if (minCapacity - elementData.length > 0)
          grow(minCapacity); // 真正擴容的地方
}
/**
 *  擴容
 */
private void grow(int minCapacity) {
        // overflow-conscious code
      // 原數(shù)組長度
      int oldCapacity = elementData.length;
      // 計算數(shù)組長度=原數(shù)組長度(默認(rèn)10) +  (capacityIncrement 不指定默認(rèn)0)擒贸,所以最終新的數(shù)組長度=10+10=20
      int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
      // 新長度驗證
      if (newCapacity - minCapacity < 0)
          newCapacity = minCapacity;
      if (newCapacity - MAX_ARRAY_SIZE > 0)
          newCapacity = hugeCapacity(minCapacity);
      // 將原數(shù)組數(shù)據(jù)復(fù)制到新的數(shù)組中
      elementData = Arrays.copyOf(elementData, newCapacity);
}

首先看方法使用synchronized進行修飾,這也就是它能保證原子性的原因觉渴,基本所有的方法都加了synchronized介劫。
屬性modCount我們先不說,后邊再統(tǒng)一說明案淋。
add主要的邏輯都在代碼里寫了注釋座韵,相信聰明的你一定能看懂。

add還有另一個方法add(int index, E element)踢京,我將它和set(int index,E element)一起說誉碴,他們很相似,但又有不同之處瓣距。
add(int index, E element)

/*
 *  什么也不干黔帕,之間丟給insertElementAt,這不就是套娃嘛
 *  相比add方法旨涝,多了一個index參數(shù)蹬屹,我們可以通過這個方法在指定位置插入一條數(shù)據(jù)侣背,也就是插隊 
 */
public void add(int index, E element) {
     insertElementAt(element, index);
}
/*
 * 真正干苦力的人
 */
public synchronized void insertElementAt(E obj, int index) {
      modCount++;
      // 如果index大于容器大小白华,之間拋異常
      if (index > elementCount) {
          throw new ArrayIndexOutOfBoundsException(index
                                                     + " > " + elementCount);
      }
      // 和add方法一樣慨默,擴容的地方
      ensureCapacityHelper(elementCount + 1);
      // 將指定位置的數(shù)據(jù)往后移動,數(shù)據(jù)量大的時候弧腥,此方法的性能是很差的
      System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
      // 在指定位置插入數(shù)據(jù)
      elementData[index] = obj;
      // 容器數(shù)據(jù)量+1
      elementCount++;
}

set(int index,E element)

/*
 *  此方法和add方法不一樣厦取,它是對指定位置的數(shù)據(jù)進行修改,然后返回修改之前的數(shù)據(jù)
 */
public synchronized E set(int index, E element) {
      // 一樣管搪,都是先判斷index
      if (index >= elementCount)
          throw new ArrayIndexOutOfBoundsException(index);
      // 先獲取到老的數(shù)據(jù)
      E oldValue = elementData(index);
      // 修改成新的數(shù)據(jù)
      elementData[index] = element;
      return oldValue;
}

源碼分析做了注釋虾攻,他們的區(qū)別如下
add 是插入數(shù)據(jù),在指定位置插入數(shù)據(jù)更鲁,數(shù)據(jù)讓后移動
set 是修改數(shù)據(jù)霎箍,將指定位置的數(shù)據(jù)進行修改,然后返回修改前的數(shù)據(jù)

我們往容器中放數(shù)據(jù)就是為了需要使用的數(shù)據(jù)讀取出來澡为,容器為我們提供了如下幾個方法
get(int index)

/*
 *  獲取指定位置的數(shù)據(jù)
 */
public synchronized E get(int index) {
    // 判斷獲取數(shù)據(jù)的位置是否存在
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
     // 獲取位置并返回
     return elementData(index);
}

firstElement()
我們可以通過此方法獲取容器中第一條數(shù)據(jù)漂坏,很簡單,不分析了

public synchronized E firstElement() {
    if (elementCount == 0) {
       throw new NoSuchElementException();
   }
    return elementData(0);
}

lastElement()
獲取容器最后一條數(shù)據(jù)

public synchronized E lastElement() {
    if (elementCount == 0) {
        throw new NoSuchElementException();
    }
    return elementData(elementCount - 1);
}

容器能插入數(shù)據(jù)媒至,肯定也能刪除數(shù)據(jù)顶别,容器為我們提供了remove方法進行刪除,源碼太簡單了拒啰,一看就明白驯绎,這里不分析了。

modCount
在源碼中多處存在這個參數(shù)谋旦,那么它有什么作用呢剩失?
modCount是Vector的父類AbstractList里的一個屬性
用于實現(xiàn)fail-fast機制(快速失敗)册着,在并發(fā)集合中拴孤,對集合進行迭代操作,若期間有其他線程對集合的結(jié)構(gòu)進行操作指蚜,此時迭代器能快速感知到乞巧,并拋出ConcurrentModificationException異常。

protected transient int modCount = 0;

該參數(shù)主要用來標(biāo)記容器中數(shù)據(jù)變更的次數(shù)摊鸡,仔細(xì)觀察發(fā)現(xiàn)在所有的數(shù)據(jù)變更的方法中都有它的影子绽媒。
它的作用是防止我們在使用迭代器遍歷的過程中,對數(shù)據(jù)進行修改免猾,迭代器在對容器進行遍歷的時候是辕,首先會判斷此屬性的值是否有變更,有變更則拋異常猎提。

我們實現(xiàn)一個迭代器遍歷容器的案例

// 創(chuàng)建一個容器
Vector vector = new Vector();
vector.add("a");
vector.add("b");
// 創(chuàng)建迭代器
Iterator iterator = vector.iterator();
System.out.println(iterator.next());
// 新增一條數(shù)據(jù)获三,則modCount++
vector.add("c");
 System.out.println(iterator.next());

輸出結(jié)果
a
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.Vector$Itr.checkForComodification(Vector.java:1210)
    at java.util.Vector$Itr.next(Vector.java:1163)
    at com.xsx.study.collection.VectorDemo.main(VectorDemo.java:17)

由于兩次next()方法中間修改了容器的數(shù)據(jù)

vector.iterator(); 其實是new Itr();創(chuàng)建一個Itr實例,Itr是Vector的一個內(nèi)部類,該內(nèi)部類實現(xiàn)Iterator接口

private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        // 關(guān)鍵在這疙教,我們創(chuàng)建一個Itr的時候棺聊,將modCount賦值給expectedModCount,next()方法會判斷這兩個值是否相等贞谓,不相等說明容器被修改過了限佩,拋異常
        int expectedModCount = modCount;
}

什么?你不信裸弦,讓你死心塌地

public E next() {
     synchronized (Vector.this) {
         // 關(guān)鍵K钔!
          checkForComodification();
          int i = cursor;
          if (i >= elementCount)
              throw new NoSuchElementException();
         cursor = i + 1;
          return elementData(lastRet = i);
       }
}
final void checkForComodification() {
        // 真像在此理疙,無話可說了吧
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
}

總結(jié)

  1. Vector內(nèi)部使用數(shù)組來存儲數(shù)據(jù)晕城,當(dāng)數(shù)組內(nèi)存不足以存儲數(shù)據(jù)時,會進行擴容窖贤,默認(rèn)在原來容器大小的基礎(chǔ)上+10砖顷,以達到擴容的目的,這也就是我們常說的動態(tài)擴容主之,但我們使用的過程中進行去指定初始大小择吊,避免擴容,因為擴容的效率和數(shù)據(jù)量成反比槽奕。
  2. Vector大部分的方法都使用synchronized進行修飾几睛,保證了線程安全,但在并發(fā)場景下效率極低粤攒,這也是我們在工作中基本不使用的原因所森。
  3. modCount屬性是父類AbstractList的一個屬性,該屬性是為了防止在使用迭代器Iterator遍歷容器的過程中對容器進行修改夯接,導(dǎo)致遍歷數(shù)據(jù)不正確焕济。

到這里我們對Vector分析也完成了,Vector的源碼是真的很簡單盔几,適合初學(xué)者看晴弃,不要害怕,干就是了逊拍,如有寫得不對的地方煩請指出上鞠,一起學(xué)習(xí)進步。

下篇文章分析ArrayList芯丧,其實看明白了Vector芍阎,ArrayList也就懂了。

我是一個愛看源碼的老謝缨恒,知道越多谴咸,不知道的越多轮听。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市岭佳,隨后出現(xiàn)的幾起案子血巍,更是在濱河造成了極大的恐慌,老刑警劉巖驼唱,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藻茂,死亡現(xiàn)場離奇詭異驹暑,居然都是意外死亡玫恳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門优俘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來京办,“玉大人,你說我怎么就攤上這事帆焕〔研觯” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵叶雹,是天一觀的道長财饥。 經(jīng)常有香客問我,道長折晦,這世上最難降的妖魔是什么钥星? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮满着,結(jié)果婚禮上谦炒,老公的妹妹穿的比我還像新娘。我一直安慰自己风喇,他們只是感情好宁改,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著魂莫,像睡著了一般还蹲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耙考,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天谜喊,我揣著相機與錄音,去河邊找鬼琳骡。 笑死锅论,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的楣号。 我是一名探鬼主播最易,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼怒坯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了藻懒?” 一聲冷哼從身側(cè)響起剔猿,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嬉荆,沒想到半個月后归敬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡鄙早,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年汪茧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片限番。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡舱污,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弥虐,到底是詐尸還是另有隱情扩灯,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布霜瘪,位于F島的核電站珠插,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏颖对。R本人自食惡果不足惜捻撑,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惜互。 院中可真熱鬧布讹,春花似錦、人聲如沸训堆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坑鱼。三九已至膘流,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲁沥,已是汗流浹背呼股。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留画恰,地道東北人彭谁。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像允扇,于是被迫代替她去往敵國和親缠局。 傳聞我的和親對象是個殘疾皇子则奥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容