1. 程式人生 > 其它 >Stack&Vector原始碼解析

Stack&Vector原始碼解析

目錄

1 Stack原始碼

前面我們已經接觸過幾種資料結構了,有陣列、連結串列、Hash表、紅黑樹(二叉查詢樹),今天再來看另外一種資料結構:

1.1 棧定義

什麼是棧,直接舉個例子,棧就相當於一個很窄的木桶,我們往木桶裡放東西,往外拿東西時會發現,我們最開始放的東西在最底部,最先拿出來的是剛剛放進去的。所以,棧就是這麼一種先進後出( First In Last Out

,或者叫後進先出) 的容器, 它只有一個口,在這個口放入元素,也在這個口取出元素

棧最主要有兩個動作就是入棧出棧操作,其實還是很容易的明白的,那麼我們接下來就看一下Jdk容器中的棧Stack是怎麼實現的吧。

 public class Stack<E> extends Vector<E> {}

我們發現Stack繼承了Vector,Vector又是什麼呢,看一下。

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

發現沒有,VectorList的一個實現類,其實Vector也是一個基於陣列實現的List容器,其功能及實現程式碼和ArrayList基本上是一樣的。
那麼不一樣的是什麼地方的:一個是陣列擴容的時候,Vector*2ArrayList*1.5+1
另一個就是Vector是執行緒安全的,而ArrayList不是,而Vector執行緒安全的做法是在每個方法上面加了一個synchronized關鍵字來保證的。但是這裡說一句,Vector已經(大家公認的)不被官方推薦使用了,正是因為其實現執行緒安全方式是鎖定整個方法,導致的是效率不高

1.2 Stack&Vector底層儲存

由於Stack

是繼承和基於Vector,那麼簡單看一下Vector的一些定義和方法原始碼:

     // 底層使用陣列儲存資料 
          protected Object[] elementData;
      // 元素個數 
           protected int elementCount ;
      // 自定義容器擴容遞增大小 
       protected int capacityIncrement ;
  
      public Vector( int initialCapacity, int capacityIncrement) {
          super();
         // 越界檢查
                  if (initialCapacity < 0)
             throw new IllegalArgumentException( "Illegal Capacity: " +
                                                initialCapacity);
         // 初始化陣列
                  this.elementData = new Object[initialCapacity];
         this.capacityIncrement = capacityIncrement;
     }
  
     // 使用synchronized關鍵字鎖定方法,保證同一時間內只有一個執行緒可以操縱該方法
          public synchronized boolean add(E e) {
         modCount++;
        // 擴容檢查
               ensureCapacityHelper( elementCount + 1);
         elementData[elementCount ++] = e;
         return true;
     }
  
     private void ensureCapacityHelper(int minCapacity) {
         // 當前元素數量
                  int oldCapacity = elementData .length;
         // 是否需要擴容
                  if (minCapacity > oldCapacity) {
            Object[] oldData = elementData;
            // 如果自定義了容器擴容遞增大小,則按照capacityIncrement進行擴容,否則按兩倍進行擴容(*2)
                        int newCapacity = (capacityIncrement > 0) ? (oldCapacity + capacityIncrement) : (oldCapacity * 2);
            if (newCapacity < minCapacity) {
               newCapacity = minCapacity;
            }
            // 陣列copy
                        elementData = Arrays.copyOf( elementData, newCapacity);
        }
}

Vector就簡單看到這裡,其他方法Stack如果沒有呼叫的話就不進行分析了,不明白的可以去看ArrayList原始碼解析

1.3 peek()——獲取棧頂的物件

      /** 
            * 獲取棧頂的物件,但是不刪除
       */ 
            public synchronized E peek() {
          // 當前容器元素個數 
                   int   len = size();
       
          // 如果沒有元素,則直接丟擲異常
             if (len == 0)
            throw new EmptyStackException();
         // 呼叫elementAt方法取出陣列最後一個元素(最後一個元素在棧頂)
                 return elementAt(len - 1);
     }
  
     /**
           * 根據index索引取出該位置的元素,這個方法在Vector中
      */
           public synchronized E elementAt(int index) {
         // 越界檢查
                if (index >= elementCount ) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
        }
 
         // 直接通過陣列下標獲取元素
                return (E)elementData [index];
}

1.4 pop()——彈棧(出棧)

彈棧(出棧),獲取棧頂的物件,並將該物件從容器中刪除

      /**       * 彈棧,獲取並刪除棧頂的物件
       */ 
            public synchronized E pop() {
          // 記錄棧頂的物件 
                  E      obj;
          // 當前容器元素個數 
                   int   len = size();
  
        // 通過peek()方法獲取棧頂物件
                obj = peek();
       // 呼叫removeElement方法刪除棧頂物件
             removeElementAt(len - 1);
 
        // 返回棧頂物件
                 return obj;
     }
 
     /**      * 根據index索引刪除元素
      */
           public synchronized void removeElementAt(int index) {
         modCount++;
         // 越界檢查
         if (index >= elementCount ) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +elementCount);
       }
       else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
         // 計算陣列元素要移動的個數
         int j = elementCount - index - 1;
         if (j > 0) {
            // 進行陣列移動,中間刪除了一個,所以將後面的元素往前移動(這裡直接移動將index位置元素覆蓋掉,就相當於刪除了)
            System. arraycopy(elementData, index + 1, elementData, index, j);
        }
         // 容器元素個數減1
         elementCount--;
         // 將容器最後一個元素置空(因為刪除了一個元素,然後index後面的元素都向前移動了,所以最後一個就沒用了 )
        elementData[elementCount ] = null; /* to let gc do its work */
}

1.5.push(E item)——壓棧(入棧)

壓棧(入棧),將物件新增進容器並返回

      /**       * 將物件新增進容器並返回
      */ 
      public E push(E item) {
         // 呼叫addElement將元素新增進容器
         addElement(item);
         // 返回該元素 
                  return item;
      }
 
     /**
      * 將元素新增進容器,這個方法在Vector中
      */
     public synchronized void addElement(E obj) {
         modCount++;
        // 擴容檢查17        ensureCapacityHelper( elementCount + 1);
        // 將物件放入到陣列中,元素個數+1
         elementData[elementCount ++] = obj;
     }

1.6 search(Object o)

search(Object o)——返回物件在容器中的位置,棧頂為1

      /**      * 返回物件在容器中的位置,棧頂為1
       */ 
      public synchronized int search(Object o) {
          // 從陣列中查詢元素,從最後一次出現
          int i = lastIndexOf(o);
  
          // 因為棧頂算1,所以要用size()-i計算 
          if (i >= 0) {
            return size() - i;
        }
         return -1;
     }

1.7 empty()——容器是否為空

     /**      * 檢查容器是否為空
      */
      public boolean empty() {
         return size() == 0;
     }

到這裡Stack的方法就分析完成了,由於Stack最終還是基於陣列的,理解起來還是很容易的(因為有了ArrayList的基礎)

1.8 stack是否可以用連結串列

雖然jdkStack的原始碼分析完了,但是這裡有必要討論下,不知道是否發現這裡的Stack很奇怪的現象,

  • Stack為什麼是基於陣列實現的呢?
    我們都知道陣列的特點:方便根據下標查詢(隨機訪問),但是記憶體固定,且擴容效率較低。很容易想到Stack用連結串列實現最合適的
  • Stack為什麼是繼承Vector
    繼承也就意味著Stack繼承了Vector的方法,這使得Stack有點不倫不類的感覺,既是List又是Stack。如果非要繼承Vector合理的做法應該是什麼:Stack不繼承Vector,而只是在自身有一個Vector的引用

唯一的解釋就是Stackjdk1.0出來的,那個時候jdk中的容器還沒有ArrayListLinkedList等只有Vector,既然已經有了Vector且能實現Stack的功能,那麼就幹吧。。。
既然用連結串列實現Stack是比較理想的,那麼我們就來嘗試一下吧:

  import java.util.LinkedList;
  
  public class LinkedStack<E> {
  
          private LinkedList<E> linked ;
  
          public LinkedStack() {
                 this.linked = new LinkedList<E>();
         }
 
         public E push(E item) {
                this.linked .addFirst(item);
                return item;
        }
 
         public E pop() {
                if (this.linked.isEmpty()) {
                       return null;
               }
                return this.linked.removeFirst();
        }
 
         public E peek() {
                if (this.linked.isEmpty()) {
                       return null;
               }
                return this.linked.getFirst();
        }
        
         public int search(E item) {
                int i = this.linked.indexOf(item);
                return i + 1;
        }
        
         public boolean empty() {
                return this.linked.isEmpty();
        }
}

這裡使用的LinkedList實現的Stack,記得在LinkedList中說過,LinkedList實現了Deque介面使得它既可以作為棧(先進後出),又可以作為佇列(先進先出)