容器類框架分析(3)(java)List 容器源碼分析的補(bǔ)充--Vector 和 Stack

移步數(shù)據(jù)結(jié)構(gòu)--容器匯總(java & Android)
內(nèi)容:

  1. Vector 介紹及與 ArrayList 的區(qū)別
  2. ArrayList 與 LinkedList 的區(qū)別
  3. Stack 類的介紹及實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 Stack
  4. SynchronizedList 與 Vector的區(qū)別


    image

1 Vector

  • Vector 是一個(gè)相當(dāng)古老的 Java 容器類,始于 JDK 1.0,并在 JDK 1.2 時(shí)代對(duì)其進(jìn)行修改,使其實(shí)現(xiàn)了 List 和 Collection 。從作用上來看,Vector 和 ArrayList 很相似,都是內(nèi)部維護(hù)了一個(gè)可以動(dòng)態(tài)變換長(zhǎng)度的數(shù)組。
  • 但是他們的擴(kuò)容機(jī)制卻不相同。對(duì)于 Vector 的源碼大部分都和 ArrayList 差不多,這里簡(jiǎn)單看下 Vector 的構(gòu)造函數(shù),以及 Vector 的擴(kuò)容機(jī)制。
    Java容器類框架分析(1)ArrayList源碼分析

1.1 構(gòu)造函數(shù)

  • Vector 的構(gòu)造函數(shù)可以指定內(nèi)部數(shù)組的初始容量和擴(kuò)容系數(shù),如果不指定初始容量默認(rèn)初始容量為 10,但是不同于 ArrayList 的是它在創(chuàng)建的時(shí)候就分配了容量為10的內(nèi)存空間,而 ArrayList 則是在第一次調(diào)用 add 的時(shí)候才生成一個(gè)容量為 10 數(shù)組。
public Vector() {
   this(10);//創(chuàng)建一個(gè)容量為 10 的數(shù)組。
}
    
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;
}

// 此方法在 JDK 1.2 后添加
public Vector(Collection<? extends E> c) {
   elementData = c.toArray();//創(chuàng)建與參數(shù)集合長(zhǎng)度相同的數(shù)組
   elementCount = elementData.length;
   // c.toArray might (incorrectly) not return Object[] (see 6260652)
   if (elementData.getClass() != Object[].class)
       elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

1..2 擴(kuò)容機(jī)制

private void grow(int minCapacity) {
   // overflow-conscious code
   int oldCapacity = elementData.length;
   // 如果我們沒有指定擴(kuò)容系數(shù),那么 newCapacity = 2 * oldCapacity 
   // 如果我們指定了擴(kuò)容系數(shù),那么每次增加指定的容量
   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);
}
  • 由上邊的方法結(jié)合我們的構(gòu)造函數(shù),我們便可知道 Vector 的需要擴(kuò)容的時(shí)候,首先會(huì)判斷 capacityIncrement 即在構(gòu)造的 Vector 的時(shí)候時(shí)候指定了擴(kuò)容系數(shù),如果指定了則按照指定的系數(shù)來擴(kuò)大容量,擴(kuò)大后新的容量為 oldCapacity + capacityIncrement,如果沒有指定capacityIncrement的大小,則默認(rèn)擴(kuò)大原來容量的一倍,這點(diǎn)不同于 ArrayList 的 0.5 倍長(zhǎng)度
  • 對(duì)于 Vector 與 ArrayList 的區(qū)別最重要的一點(diǎn)是 Vector所有的訪問內(nèi)部數(shù)組的方法都帶有synchronized,這意味著 Vector 是線程安全的,而ArrayList 并沒有這樣的特性。

1.3 elements()

  • 對(duì)于 Vector 而言,除了 for 循環(huán),高級(jí) for 循環(huán),迭代的迭代方法外,還可以調(diào)用 elements() 返回一個(gè) Enumeration 。
  • Enumeration 是一個(gè)接口,其內(nèi)部只有兩個(gè)方法hasMoreElements 和 nextElement,看上去和迭代器很相似,但是并沒迭代器的 add remove,只能作用于遍歷
public interface Enumeration<E> {
    boolean hasMoreElements();
    E nextElement();
}
// Vector 的 elements 方法。
public Enumeration<E> elements() {
   return new Enumeration<E>() {
       int count = 0;

       public boolean hasMoreElements() {
           return count < elementCount;
       }

       public E nextElement() {
           synchronized (Vector.this) {
               if (count < elementCount) {
                   return elementData(count++);
               }
           }
           throw new NoSuchElementException("Vector Enumeration");
       }
   };
}
    

使用方法

Vector<String> vector = new Vector<>();
vector.add("1");
vector.add("2");
vector.add("3");

Enumeration<String> elements = vector.elements();

while (elements.hasMoreElements()){
  System.out.print(elements.nextElement() + " ");
}

事實(shí)上,這個(gè)接口也是很古老的一個(gè)接口,JDK 為了適配老版本,我們可以調(diào)用類似 Enumeration<String> enumeration = Collections.enumeration(list); 來返回一個(gè)Enumeration 。其原理就是調(diào)用對(duì)應(yīng)的迭代器的方法。

// Collections.enumeration 方法
public static <T> Enumeration<T> enumeration(final Collection<T> c) {
   return new Enumeration<T>() {
        // 構(gòu)造對(duì)應(yīng)的集合的迭代器
       private final Iterator<T> i = c.iterator();
        // 調(diào)用迭代器的 hasNext
       public boolean hasMoreElements() {
           return i.hasNext();
       }
       // 調(diào)用迭代器的 next
       public T nextElement() {
           return i.next();
       }
   };
}

1.4 Vector 與 ArrayList 的比較

  1. Vector 與 ArrayList 底層都是數(shù)組數(shù)據(jù)結(jié)構(gòu),都維護(hù)著一個(gè)動(dòng)態(tài)長(zhǎng)度的數(shù)組。
  2. Vector 對(duì)擴(kuò)容機(jī)制在沒有通過構(gòu)造指定擴(kuò)大系數(shù)的時(shí)候,默認(rèn)增長(zhǎng)現(xiàn)有數(shù)組長(zhǎng)度的一倍。而 ArrayList 則是擴(kuò)大現(xiàn)有數(shù)組長(zhǎng)度的一半長(zhǎng)度。
  3. Vector 是線程安全的, 而 ArrayList 不是線程安全的,在不涉及多線程操作的時(shí)候 ArrayList 要比 Vector 效率高
  4. 對(duì)于 Vector 而言,除了 for 循環(huán),高級(jí) for 循環(huán),迭代器的迭代方法外,還可以調(diào)用 elements() 返回一個(gè) Enumeration 來遍歷內(nèi)部元素。

2 Stack 介紹

由開始的繼承體系可以知道 Stack 繼承自 Vector,也就是 Stack 擁有 Vector 所有的增刪改查方法。
我們先來看下棧的定義:

棧(stack)又名堆棧,它是一種運(yùn)算受限的線性表。其限制是僅允許在表的一端進(jìn)行插入和刪除運(yùn)算。這一端被稱為棧頂,相對(duì)地,把另一端稱為棧底。向一個(gè)棧插入新元素又稱作進(jìn)棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素;從一個(gè)棧刪除元素又稱作出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素。

簡(jiǎn)單來說,棧這種數(shù)據(jù)結(jié)構(gòu)有一個(gè)約定,就是向棧中添加元素和從棧中取出元素只允許在棧頂進(jìn)行,而且先入棧的元素總是后取出。 我們可以用數(shù)組和鏈表來實(shí)現(xiàn)棧的這種數(shù)據(jù)結(jié)構(gòu)的操作。

一般來說對(duì)于棧有一下幾種操作:

  1. push 入棧
  2. pop 出棧
  3. peek 查詢棧頂
  4. empty 棧是否為空

Java 中的 Stack 容器是以數(shù)組為底層結(jié)構(gòu)來實(shí)現(xiàn)棧的操作的,通過調(diào)用 Vector 對(duì)應(yīng)的添加刪除方法來實(shí)現(xiàn)入棧出站操作。

// 入棧
public E push(E item) {
   addElement(item);//調(diào)用 Vector 定義的 addElement 方法

   return item;
}
// 出棧
public synchronized E pop() {
   E       obj;
   int     len = size();

   obj = peek();
   removeElementAt(len - 1);//調(diào)用 Vector 定義的 removeElementAt 數(shù)組末尾的元素的方法 

   return obj;
}
// 查詢棧頂元素
public synchronized E peek() {
   int     len = size();

   if (len == 0)
       throw new EmptyStackException();
   return elementAt(len - 1);//查詢數(shù)組最后一個(gè)元素。
}

上邊簡(jiǎn)單介紹了 Java 容器中的 Stack 實(shí)現(xiàn),但是事實(shí)上官方并不推薦在使用這些陳舊的集合容器類。對(duì)于棧從數(shù)據(jù)結(jié)構(gòu)上而言,相對(duì)于線性表,其實(shí)現(xiàn)也存在,順序存儲(chǔ)(數(shù)組),非連續(xù)存儲(chǔ)(鏈表)的實(shí)現(xiàn)方法。而我們文章最后看到的 Java容器類框架分析(2)LinkedList源碼分析是可以取代 Stack來進(jìn)行棧操作的。

public class SimpleStack<E> {
    //默認(rèn)容量
    private static final int DEFAULT_CAPACITY = 10;
    //棧中存放元素的數(shù)組
    private Object[] elements;
    //棧中元素的個(gè)數(shù)
    private int size = 0;
    //棧頂指針
    private int top;


    public SimpleStack() {
        this(DEFAULT_CAPACITY);
    }

    public SimpleStack(int initialCapacity) {
        elements = new Object[initialCapacity];
        top = -1;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public int size() {
        return size;
    }

    @SuppressWarnings("unchecked")
    public E pop() throws Exception {
        if (isEmpty()) {
            throw new EmptyStackException();
        }

        E element = (E) elements[top];
        elements[top--] = null;
        size--;
        return element;
    }

    @SuppressWarnings("unchecked")
    public E peek() throws Exception {
        if (isEmpty()) {
            throw new Exception("當(dāng)前棧為空");
        }
        return (E) elements[top];
    }

    public void push(E element) throws Exception {
        //添加之前確保容量是否滿足條件
        ensureCapacity(size + 1);
        elements[size++] = element;
        top++;
    }

    private void ensureCapacity(int minSize) {
        if (minSize - elements.length > 0) {
            grow();
        }
    }

    private void grow() {
        int oldLength = elements.length;
        // 更新容量操作 擴(kuò)充為原來的1.5倍 這里也可以選擇其他方案
        int newLength = oldLength + (oldLength >> 1);
        elements = Arrays.copyOf(elements, newLength);
    }
}

3 同步 vs 非同步

  • 對(duì)于 Vector 和 Stack 從源碼上他們?cè)趯?duì)應(yīng)的增刪改查方法上都使用 synchronized關(guān)鍵字修飾了方法,這也就代表這個(gè)方法是同步方法,線程安全的。
  • 不過我們?cè)诮榻B ArrayList 和 LinkedList 的時(shí)候提及到了我們可以使用Collections 的靜態(tài)方法,將一個(gè) List 轉(zhuǎn)化為線程同步的 List:
List<Integer> synchronizedArrayList = Collections.synchronizedList(arrayList);
List<Integer> synchronizedLinkedList = Collections.synchronizedList(linkedList);

那么這里又有一道面試題是這樣問的:

請(qǐng)簡(jiǎn)述一下 Vector 和 SynchronizedList 區(qū)別,

SynchronizedList 即Collections.synchronizedList(arrayList); 后生成的List 類型,它本身是 Collections 一個(gè)內(nèi)部類。

static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
   private static final long serialVersionUID = -7754090372962971524L;

   final List<E> list;

   SynchronizedList(List<E> list) {
       super(list);
       this.list = list;
   }
   SynchronizedList(List<E> list, Object mutex) {
       super(list, mutex);
       this.list = list;
   }
   
   .....
}

對(duì)于 SynchronizedList 構(gòu)造可以看到有一個(gè) Object 的參數(shù),但是看到 mutex 這個(gè)單詞應(yīng)該就明白了這個(gè)參數(shù)的含義了,就是同步鎖,其實(shí)我們點(diǎn)擊 super 方法可以看到,單個(gè)參數(shù)的構(gòu)造函數(shù)鎖就是其對(duì)象自身。

SynchronizedCollection(Collection<E> c) {
       this.c = Objects.requireNonNull(c);
       mutex = this;
   }

SynchronizedCollection(Collection<E> c, Object mutex) {
  this.c = Objects.requireNonNull(c);
  this.mutex = Objects.requireNonNull(mutex);
}

接下來我們看看增刪改查方法吧:

   public E get(int index) {
       synchronized (mutex) {return list.get(index);}
   }
   public E set(int index, E element) {
       synchronized (mutex) {return list.set(index, element);}
   }
   public void add(int index, E element) {
       synchronized (mutex) {list.add(index, element);}
   }
   public E remove(int index) {
       synchronized (mutex) {return list.remove(index);}
   }

   public int indexOf(Object o) {
       synchronized (mutex) {return list.indexOf(o);}
   }
   public int lastIndexOf(Object o) {
       synchronized (mutex) {return list.lastIndexOf(o);}
   }

   public boolean addAll(int index, Collection<? extends E> c) {
       synchronized (mutex) {return list.addAll(index, c);}
   }
   //注意這里沒加 synchronized(mutex)
   public ListIterator<E> listIterator() {
       return list.listIterator(); // Must be manually synched by user
   }

   public ListIterator<E> listIterator(int index) {
       return list.listIterator(index); // Must be manually synched by user
   }

可以很清楚的看到,讓一個(gè)集合變成線程安全的,Collocations 只是包裝了參數(shù)集合的增刪改查方法,加了同步的限制。與 Vector 相比可以看出來,兩者第一個(gè)區(qū)別在于是同步方法還是同步代碼塊,對(duì)于這兩個(gè)區(qū)別如下:

  1. 同步代碼塊在鎖定的范圍上可能比同步方法要小,一般來說鎖的范圍大小和性能是成反比的。
  2. 同步塊可以更加精確的控制鎖的作用域(鎖的作用域就是從鎖被獲取到其被釋放的時(shí)間),同步方法的鎖的作用域就是整個(gè)方法。

由上述兩個(gè)方法看出來,``Collections.synchronizedList(arrayList);生成的同步集合看起來更高效一些,其實(shí)這種差異在 Vector 和 ArrayList上體現(xiàn)的很不明顯,因?yàn)槠?add 方法內(nèi)部實(shí)現(xiàn)大致相同。而從構(gòu)造參數(shù)上來看Vector不能像SynchronizedList` 一樣指定加鎖對(duì)象。

而我們也看到了 SynchronizedList 并沒有給迭代器進(jìn)行加鎖,但是翻看 Vector 的迭代器方法確實(shí)枷鎖的,所以我們?cè)谑褂肧ynchronizedList的的迭代器的時(shí)候需要手動(dòng)做同步處理:

  synchronized (list) {
    Iterator i = list.iterator(); // Must be in synchronized block
    while (i.hasNext())
        foo(i.next());
 }

至此我們可以總結(jié)出 SynchronizedList與 Vector的三點(diǎn)差異:

  1. SynchronizedList 作為一個(gè)包裝類,有很好的擴(kuò)展和兼容功能。可以將所有的 List 的子類轉(zhuǎn)成線程安全的類。
  2. 使用 SynchronizedList 的獲取迭代器,進(jìn)行遍歷時(shí)要手動(dòng)進(jìn)行同步處理,而 Vector 不需要。
  3. SynchronizedList 可以通過參數(shù)指定鎖定的對(duì)象,而 Vector 只能是對(duì)象本身。

參考

Java List 容器源碼分析的補(bǔ)充

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,541評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,896評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,062評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,356評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,555評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,769評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,289評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,516評(píng)論 2 379

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