源码解读--Stack

今天来说说java里面的栈。栈是一种先进后出或者后进先出的一种数据结构。先来看下图谱。java源码系列


Stack继承了vector,大家都知道vector是线程安全的。

为什么是线程安全的呢?因为方法前面用了synchronized修饰。

为什么用了synchronized就是线程安全的呢?因为synchronized内部有锁的概念,线程在没有获取的资源的时候放入entry set,获取到了资源就是owner,放弃现有资源方法就是wait set。使用的是monitor锁实现,用javap 命令可以直观的看到monitor加锁过程。



好了,前戏就简单讲到这里了,现在来看看java栈的源码实现。

一 .入栈

public Stack() {  栈的构造函数
}
public E push(E item) {  //添加item元素,这里用的是泛型
    addElement(item);  //这个方法是Vector里面的方法,是一个同步方法
    return item;
}
public synchronized void addElement(E obj) {
    modCount++;   //修改记录数
    ensureCapacityHelper(elementCount + 1);  //确保容量方法。为什么要这个方法,接口往下看
    elementData[elementCount++] = obj;  //值赋上去,数值长度加1
}
private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code  if (minCapacity - elementData.length > 0)  //怎么还有比较?不出惊讶,用的是数组,默认是10,
        grow(minCapacity);  //扩容,和Arraylist源码里面的方法貌似差不多
}
private void grow(int minCapacity) {  //注意啦,扩容开始
    // overflow-conscious code  int oldCapacity = elementData.length;  //保存数组现在的长度
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?  //如果调用Vector的默认构造方法,capacityIncrement为0,否则capacityIncrement有具体的值,具体请看Vector(int,int)方法
                                     capacityIncrement : oldCapacity);  //如果capacityIncrement默认是0,数组的长度扩大一倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)  //如果数组长度扩大的查过数组最大长度
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow  throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?  //确定最大取值
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

好了,到此栈的添加就完成了,现在来总结一下:

1.确定数组长度,默认是10

2.看看添加的值需不需要扩容,如果要扩容就将数组的长度加一倍,2n

3.将值添加进去

二.获取栈顶元素

public synchronized E peek() {  //这是一个同步方法
    int len = size();  //获取栈中元素的个数
    if (len == 0)  //栈里面没有值,就抛出一个栈空的异常
        throw new EmptyStackException();
    return elementAt(len - 1);  //获取栈顶的元素
}
public synchronized E elementAt(int index) {
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
    }
    return elementData(index);
}
 
 
E elementData(int index) {
    return (E) elementData[index];
}

三.出栈 

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

    obj = peek();  //获取栈顶元素
    removeElementAt(len - 1);  //删除栈顶元素
    return obj;
}
public synchronized void removeElementAt(int index) {  //栈顶元素的索引
    modCount++;
    if (index >= elementCount) {  //如果要删除的索引大于数组的长度,抛出一个数组越界异常。
        throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                 elementCount);
    }
    else if (index < 0) {  //如果索引小于0 也要抛出异常
        throw new ArrayIndexOutOfBoundsException(index);
    }
    int j = elementCount - index - 1;  //需要赋值的长度,由于栈是删除栈顶元素,这里是0
    if (j > 0) {
        System.arraycopy(elementData, index + 1, elementData, index, j);  //
    }
    elementCount--;  //数组长度减一
    elementData[elementCount] = null;   //将栈顶元素的赋值为null,变成不可引用对象,后面gc会自行收集,腾出内存空间 }

四.查询栈元素索引

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) {  //如果查询的对象是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判断,其实这个方法现在可以改一版了,方便理解用Objects.equals(jdk7)
                return i;
    }
    return -1;  //没有找到索引就是-1
}

到此,栈的源码解读就完全讲完了。在java中,栈使用的数组。若果让你用链表来实现,你会么?

给一个简单提示哈。

1入栈,用头插法将数据插入到对列中。主要是出栈的时间变短。

2获取栈顶元素,head->next就是栈顶元素了,时间复杂度为o(1)。

3出栈,也只要删除head->next就可以,时间复杂度也会o(1)。

4查询栈索引,从链表头一直循环比较过去,最坏的情况也是o(n)。

以上就是用链表来实现栈。个人感觉比数组实现好很多。