原始碼閱讀之Java棧的實現

hylinux1024發表於2018-11-06

0x00 棧

棧是 Last-In-First-Out (後進先出)的線性表。對棧的操作主要有兩個:入棧(push)和出棧(pop)。因此它也是一種操作受限的線性表。儘管如此,它在計算機中應用非常廣泛,是一種非常基礎的資料結構。

0x01 原始碼

從原始碼中可以看出棧也是一種非常簡單的資料結構。棧的原始碼非常簡潔,只有100多行程式碼。

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

    //入棧操作
    public E push(E item) {
        addElement(item);

        return item;
    }

    //出棧操作
    public synchronized E pop() {
        E       obj;
        int     len = size();

        obj = peek();
        removeElementAt(len - 1);

        return obj;
    }

    //檢視棧元素,不會刪除
    public synchronized E peek() {
        int     len = size();

        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }

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

    //查詢元素,如果找到則返回從棧頂開始計數的位置,否則返回-1
    public synchronized int search(Object o) {
        int i = lastIndexOf(o);

        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = 1224463164541339165L;
}

它繼承於 Vector 類,這個跟前面講的 ArrayList 是一樣的,只不過 Vector 類方法是同步的,因此 Stack 也是執行緒安全的。

push

入棧

public E push(E item) {
    addElement(item);

    return item;
}

雖然此方法沒有宣告 synchronized,但內部呼叫一個 addElement 來實現入棧操作。這個操作方法是在父類中實現的,它被宣告為執行緒安全的,因此它也是執行緒安全的。

public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
}

private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
//擴容方法
//這裡跟 ArrayList 擴容演算法有點不一樣,ArrayList 一次是擴容為原來的1.5倍,Vector 預設擴容為原來的1倍
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    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);
}

雖然 push 方法沒有宣告 synchronizedaddElement 方法中有。在這個方法裡

  • 首先把 modCount 加 1,記錄一次對資料結構的操作。
  • 然後檢查陣列容量是否足夠,不夠則擴容。
  • 最後把元素物件新增到陣列末尾。

需要注意的是Vector的擴容策略是預設一次擴容為原來的1倍,這與ArrayList 一次擴容原來的1.5倍不同

pop

出棧

public synchronized E pop() {
    E       obj;
    int     len = size();

    obj = peek();
    removeElementAt(len - 1);

    return obj;
}

pop 方法被宣告為 synchronized ,是執行緒安全的方法。它通過 peek 方法獲取到棧頂元素物件,然後呼叫父類的 removeElementAt 方法把棧頂元素刪除。

peek

檢視棧頂元素

public synchronized E peek() {
    int     len = size();

    if (len == 0)
        throw new EmptyStackException();
    return elementAt(len - 1);
}

這個方法也是執行緒安全的。可以看出如果棧的大小為0時,執行 peek 方法會丟擲 EmptyStackException 異常。

search

在棧中搜尋元素

public synchronized int search(Object o) {
    int i = lastIndexOf(o);

    if (i >= 0) {
        return size() - i;
    }
    return -1;
}

繼續檢視父類程式碼

public synchronized int lastIndexOf(Object o) {
    return lastIndexOf(o, elementCount-1);
}

public synchronized int lastIndexOf(Object o, int index) {
    if (index >= elementCount)
        throw new IndexOutOfBoundsException(index + " >= "+ elementCount);

    if (o == null) {//如果查詢的元素是空的則遍歷找到最接近棧的空元素
        for (int i = index; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = index; i >= 0; i--)
            if (o.equals(elementData[i]))//通過equals方法來判斷兩個元素是否相同
                return i;
    }
    return -1;
}

在棧中查詢一個元素需要遍歷,時間複雜度為O(n)。

0x02 總結

棧是一種LIFO的資料結構,它基於 Vector 來實現,所有的方法都是執行緒安全的。入棧和出棧操作的時間複雜度為O(1),查詢元素的時間複雜度為O(n)。


相關文章