从Java底层分析Stack(栈)的用法——源码分析系列

Posted 来老铁干了这碗代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Java底层分析Stack(栈)的用法——源码分析系列相关的知识,希望对你有一定的参考价值。

最近找工作时,面试了阿里、百度、腾讯、字节等一线互联网大厂。

发现这些大厂有一个共同特征,就是喜欢考查面试者查看底层代码的能力。

于是就有了这个系列, 希望能够帮到大家。



Stack底层源码分析

先来看一下Stack的全部源码

package java.util;

public class Stack<E> extends Vector<E> {
    private static final long serialVersionUID = 1224463164541339165L;

    public Stack() {
    }

    public E push(E var1) {
        this.addElement(var1);
        return var1;
    }

    public synchronized E pop() {
        int var2 = this.size();
        Object var1 = this.peek();
        this.removeElementAt(var2 - 1);
        return var1;
    }

    public synchronized E peek() {
        int var1 = this.size();
        if (var1 == 0) {
            throw new EmptyStackException();
        } else {
            return this.elementAt(var1 - 1);
        }
    }

    public boolean empty() {
        return this.size() == 0;
    }

    public synchronized int search(Object var1) {
        int var2 = this.lastIndexOf(var1);
        return var2 >= 0 ? this.size() - var2 : -1;
    }
}

我们发现,Stack是继承Vector实现栈原理的。

接下来一个一个方法来进行分析:

1. 声明栈

声明语句:Stack<Integer>st = new Stack<Integer>();
注意:Stack中只能压入包装类,即只能声明Integer,没办法声明int。

压入:Push()

源码:

// push源码
    public E push(E var1) {
        this.addElement(var1);
        return var1;
    }

这里我们发现它代用了一个addElement方法。这个方法基于synchronized修饰,是一个线程安全的方法。

点进去查看, 发现跳转到了vector源代码中,证明Stack与Vector共用一个添加元素的方法(addElement)。

该方法首先要判断数组的容量是否能够容纳新的元素,若不能,则需要进行扩容操作,然后将元素e放置在数组的size位置。ensureCapacityHelper(int)方法源码如下

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

在ensureCapacityHelper()方法中,判断数组实际容纳的元素数量是否超过了当前数组长度,若是,则执行扩容操作, 即grow方法。

// ensureCapacityHelper 方法
    private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        // 如果超过了数组长度,则需要扩容。
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
// grow 方法
private void grow(int minCapacity) {
    // overflow-conscious code
    // 获取elementData的原始容量
    int oldCapacity = elementData.length;
    // 计算新的容量
    // 如果在构造方法中设置了capacityIncrement > 0,那么新数组长度就是原数组长度 + capacityIncrement
    // 否则,新数组长度就是原数组长度 * 2
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                        capacityIncrement : oldCapacity);
    // 若进行扩容后,capacity仍然比实际需要的小,则新容量更改为实际需要的大小,即minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果新数组的长度比虚拟机能够提供给数组的最大存储空间大,则将新数组长度更改为最大正数:Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 按照新的容量newCapacity创建一个新数组,然后再将原数组中的内容copy到新数组中
    elementData = Arrays.copyOf(elementData, newCapacity);
}

注意:扩容后的数组长度为原数组长度*2

以上,即为Stack中push的全部源码分析。


弹出:Pop()

Pop的源码:

  • 该方法被synchronized修饰,是线程安全的。

  • var2为当前栈长度,var1为栈顶元素,首先通过removeElementAt()方法移除栈顶元素,然后返回被移除的栈顶元素值。

    public synchronized E pop() {
        int var2 = this.size();
        Object var1 = this.peek();
        this.removeElementAt(var2 - 1);
        return var1;
    }

接下来查看removeElementAt()方法

    public synchronized void removeElementAt(int index) {
    // index是移除一个元素后数组的长度
    // index大于等于元素个数,则抛出异常
        modCount++;
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
    // index小于0,则抛异常
    // ArrayIndexOutOfBoundsException:数组索引越界异常
        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()

如果为0,则返回空栈异常

反之,进行元素查找并返回的操作(elementAt()方法)

    public synchronized E peek() {
        int var1 = this.size();
        if (var1 == 0) {
            throw new EmptyStackException();
        } else {
            return this.elementAt(var1 - 1);
        }
    }

判断是否为空:empty()

    public boolean empty() {
        return this.size() == 0;
    }

查找:search()

lastIndexOf()方法:从后向前查找元素,若查找成功,则返回位置,反之,返回-1。

var2存储查找结果,如果var2>=0,则证明查找成功,则返回位置

    public synchronized int search(Object var1) {
        int var2 = this.lastIndexOf(var1);
        return var2 >= 0 ? this.size() - var2 : -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]))
                    return i;
        }
        return -1;
    }

以上是关于从Java底层分析Stack(栈)的用法——源码分析系列的主要内容,如果未能解决你的问题,请参考以下文章

Java基础干货Stack源码剖析

java集合13——— Stack源码分析走一波

Stack源码分析

Java集合源码分析

Java集合源码分析

java Stack源码解析