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
發現沒有,Vector
是List
的一個實現類,其實Vector
也是一個基於陣列實現的List
容器,其功能及實現程式碼和ArrayList
基本上是一樣的。
那麼不一樣的是什麼地方的:一個是陣列擴容的時候,Vector
是*2
,ArrayList
是*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是否可以用連結串列
雖然jdk
中Stack
的原始碼分析完了,但是這裡有必要討論下,不知道是否發現這裡的Stack
很奇怪的現象,
Stack
為什麼是基於陣列實現的呢?
我們都知道陣列的特點:方便根據下標查詢(隨機訪問),但是記憶體固定,且擴容效率較低。很容易想到Stack
用連結串列實現最合適的Stack
為什麼是繼承Vector
的
繼承也就意味著Stack
繼承了Vector
的方法,這使得Stack
有點不倫不類的感覺,既是List
又是Stack
。如果非要繼承Vector
合理的做法應該是什麼:Stack
不繼承Vector
,而只是在自身有一個Vector
的引用
唯一的解釋就是Stack
是jdk1.0
出來的,那個時候jdk
中的容器還沒有ArrayList
、LinkedList
等只有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
介面使得它既可以作為棧(先進後出),又可以作為佇列(先進先出)