【数据结构】Java中栈的常用方法及模拟实现

概述

栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素的操作。进行数据插入和删除操作的一端称为栈顶,另一端称则为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

入栈:栈的插入操作叫做压栈或进栈。
出栈:栈的删除操作叫做出栈。

在这里插入图片描述

Stack

在这里插入图片描述
从上面的关系图可以看出Stack继承自Vector类,而Vertor类又继承了抽象类AbstractList,同时实现了List接口,所以已经可以猜到stack底层的结构其实是一个Collection集合。

由于Stack继承了Vector类,所以在Stack里面主要实现了下面几个方法:

方法名返回值类型说明
push(E item)E将元素压入栈中
pop( )E弹出栈顶元素
peek( )E返回栈顶元素(不删除)
empty( )boolean判断栈是否为空
search(Object o)int返回一个对象在此堆栈上的基于1的位置

下面两个方法需要说明一下:

1.push
入栈操作在Java中的源码:

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

    return item;
}

在源码中并没有看到入栈过程的详细过程,而是用addElement方法把后面的细节都封装起来了,其实主要过程就是将要入栈的元素在数组中进行尾插,再将数组使用长度加1。

深究一下就会发现一个问题,数组的长度是有限的,当我们在new一个栈的时候,就会给我们生成一个初始内部容量为10的数组。插满后便需要进行扩容,而扩容并不是无限制,所以当达到一定程度的时候便不能扩容了,就需要抛出异常。

好在这两点它的父类Vector类都考虑到了:

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);
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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);
}

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}

可以看到在grow()方法中,用一个capacityIncrement来指定扩容长度。默认的情况下相当于数组长度翻倍,如果设置了capacityIncrement变量就增加这个变量指定的这么多。
然后再调用Arrays.copyOf( )方法,将当前数组的元素给拷贝过去。

2.pop

pop()方法即弹出栈顶元素,如果栈顶没有元素就抛出异常。

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

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

        return obj;
}

在源码中可以看到在pop()方法中调用了peek()方法,并将其作为返回值返回,其中栈为空则抛出异常的部分也在peek()方法中实现。

在返回之前使用removeElementAt()方法来删除栈顶元素。

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()取到顶端的元素之后,为什么要用removeElementAt()方法将最顶端的元素移除呢?明明有一个elementCount来记录栈的长度,所以不可以不管这个元素么?
实际上,如果不管它,那么在程序运行的时候就会有一个潜在的内存泄露的问题。因为在java里面,如果我们普通定义的类型属于强引用类型。比如这里vector就底层用的Object[]这个数组强类型来保存数据。强类型在jvm中做gc的时候,只要程序中有引用到它,它是不会被回收的。这就意味着,只要我们一直在用着stack,那么stack里面所有关联的元素就都释放不了。这样运行时间一长就会导致内存泄露的问题。所以,为了解决这个问题,这里就使用了removeElementAt()方法。

用数组模拟实现一个栈

import java.util.Arrays;

class MyStack<E> {
    int capacity = 10;
    private E[] array =(E[]) new Object[capacity];
    private int size = 0;
    public void push(E e) {
        //插满了则按照2倍的方式进行扩容
        if(size == capacity) {
            capacity *= 2;
            array = Arrays.copyOf(array,capacity);
        }
        array[size] = e;
        size++;
    }
    public E pop() {
        if(empty()){
            //源码中是抛出一个异常,简易起见直接返回null
            return null;
        }
        E e = array[size-1];
        size--;
        return e;
    }
    public E peek() {
        if(empty()){
            //也是该抛出一个异常
            return null;
        }
        return array[size-1];
    }
    boolean empty() {
        return size == 0;
    }
    public int size() {
        return size;
    }
}

public class TestDemo {
    public static void main(String[] args) {
        MyStack<Integer> ms = new MyStack<>();
        ms.push(1);
        ms.push(2);
        ms.push(3);
        ms.push(4);
        System.out.println(ms.size());
        System.out.println(ms.peek());
        ms.pop();
        ms.pop();
        System.out.println(ms.size());
        System.out.println(ms.peek());
    }
}
//执行结果
4
4
2
2
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值