前言
集合原始碼分析系列:Java集合原始碼分析
前面已經把Vector
,ArrayList
,LinkedList
分析完了,本來是想開始Map
這一塊,但是看了下面這個介面設計框架圖:整個介面框架關係如下(來自百度百科):
原來還有一個漏網之魚,Stack
棧的是掛在Vector
下,前面我們已經分析過Vector
了,那麼順便把Stack
分析一遍。再不寫就2022年了:
Stack介紹
棧是一種資料結構,並不是Java
特有的,在Java
裡面體現是Stack
類。它的本質是先進後出,就像是一個桶,只能不斷的放在上面,取出來的時候,也只能不斷的取出最上面的資料。要想取出底層的資料,只有等到上面的資料都取出來,才能做到。當然,如果有這種需求,我們一般會使用雙向佇列。
以下是棧的特性演示:
Stack
在Java
中是繼承於Vector
,這裡說的是1.8
版本,共用了Vector
底層的資料結構,底層都是使用陣列實現的,具有以下的特點:
- 先進後出(
`FILO
) - 繼承於
Vector
,同樣基於陣列實現 - 由於使用的幾乎都是
Vector
,Vector
的操作都是執行緒安全的,那麼Stack
操作也是執行緒安全的。
類定義原始碼:
public
class Stack<E> extends Vector<E> {
}
成員變數只有一個序列化的變數:
private static final long serialVersionUID = 1224463164541339165L;
方法解讀
Stack
沒有太多自己的方法,幾乎都是繼承於Vector
,我們可以看看它自己擴充的 5 個方法:
E push(E item)
: 壓棧synchronized E pop()
: 出棧synchronized E peek()
:獲取棧頂元素boolean empty()
:判斷棧是不是為空int search(Object o)
: 搜尋某個物件在棧中的索引位置
push 方法
在底層實際上呼叫的是addElement()
方法,這是Vector
的方法:
public E push(E item) {
addElement(item);
return item;
}
在前面我們已經分析過Vecor
的原始碼,感興趣可以參考:http://aphysia.cn/archives/ja...
addElement
是執行緒安全的,在底層實際上就是往陣列後面新增了一個元素,但是它幫我們保障了容量,如果容量不足,會觸發自動擴容機制。
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
pop 方法
底層是先呼叫peek()
方法,獲取到棧頂元素,再呼叫removeElementAt()
方法移除掉棧頂元素,實現出棧效果。
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
removeElementAt(int index)
也是Vector
的方法,synchronized
修飾,也是執行緒安全的,由於移除的是陣列最後的元素,所以在這裡不會觸發元素複製,也就是System.arraycopy(elementData, index + 1, elementData, index, j);
:
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) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}
peek 方法
獲取棧頂元素,先獲取陣列的大小,然後再呼叫Vector
的E elementAt(int index)
獲取該索引的元素:
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
E elementAt(int index)
的原始碼如下,裡面邏輯比較簡單,只有陣列越界的判斷:
public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}
return elementData(index);
}
empty 方法
主要是用來判空,判斷元素棧裡面有沒有元素,主要呼叫的是size()
方法:
public boolean empty() {
return size() == 0;
}
這個size()
方法其實也是Vector
的方法,返回的其實也是一個類變數,元素的個數:
public synchronized int size() {
return elementCount;
}
search方法
這個方法主要用來查詢某個元素的索引,如果裡面存在多個,那麼將會返回最後一個元素的索引:
public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}
使用synchronized
修飾,也是執行緒安全的,為什麼需要這個方法呢?
我們知道棧是先進先出的,如果需要查詢一個元素在其中的位置,那麼需要一個個取出來再判斷,那就太麻煩了,而底層使用陣列進行儲存,可以直接利用這個特性,就可以快速查詢到該元素的索引位置。
至此,回頭一看,你是否會感到疑惑,`Stack
裡面沒有任何的資料,但是卻不斷的在運算元據,這得益於Vector
的資料結構:
// 底層陣列
protected Object[] elementData;
// 元素數量
protected int elementCount;
底層使用陣列,儲存了元素的個數,那麼操作元素其實只是不斷往陣列中插入元素,或者取出最後一個元素即可。
總結
stack
由於繼承了Vector
,因此也是執行緒安全的,底層是使用陣列儲存資料,大多數API
都是使用Vector
來儲存。它最重要的屬性是先進先出。至於陣列擴容,沿用了Vector
中的擴容邏輯。
如果讓我們自己實現,底層不一定使用陣列,使用連結串列也是能實現相同的功能的,只是在整個集合原始碼體系中,共有相同的部分,是不錯的選擇。
【作者簡介】:
秦懷,公眾號【秦懷雜貨店】作者,個人網站:http://aphysia.cn,技術之路不在一時,山高水長,縱使緩慢,馳而不息。