前面我们已经接触过几种数据结构了,有数组、链表、Hash表、红黑树(二叉查询树),今天再来看另外一种数据结构:栈。
什么是栈呢,我就不找它具体的定义了,直接举个例子,栈就相当于一个很窄的木桶,我们往木桶里放东西,往外拿东西时会发现,我们最开始放的东西在最底部,最先拿出来的是刚刚放进去的。所以,栈就是这么一种先进后出( First In Last Out,或者叫后进先出) 的容器,它只有一个口,在这个口放入元素,也在这个口取出元素。
栈最主要了两个动作就是入栈和出栈操作,其实还是很容易的明白的对不对,那么我们接下来就看一下Jdk容器中的栈Stack是怎么实现的吧。
1.定义
1 public2 class Stack<E> extends Vector<E> {
我们发现Stack继承了Vector,Vector又是什么东东呢,看一下。
1 public class Vector<E>2 extends AbstractList<E>3 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
发现没有,Vector是List的一个实现类,其实Vector也是一个基于数组实现的List容器,其功能及实现代码和ArrayList基本上是一样的。那么不一样的是什么地方的,一个是数组扩容的时候,Vector是*2,ArrayList是*1.5+1;另一个就是Vector是线程安全的,而ArrayList不是,而Vector线程安全的做法是在每个方法上面加了一个synchronized关键字来保证的。但是这里说一句,Vector已经不官方的(大家公认的)不被推荐使用了,正式因为其实现线程安全方式是锁定整个方法,导致的是效率不高,那么有没有更好的提到方案呢,其实也不能说有,但是还真就有那么一个,Collections.synchronizedList(),这不是我们今天的重点不做深入探讨,回到Stack的实现上。
2.Stack&Vector底层存储
由于Stack是继承和基于Vector,那么简单看一下Vector的一些定义和方法源码:
1 // 底层使用数组存储数据 2 protected Object[] elementData; 3 // 元素个数 4 protected int elementCount ; 5 // 自定义容器扩容递增大小 6 protected int capacityIncrement ; 7 8 public Vector( int initialCapacity, int capacityIncrement) { 9 super();10 // 越界检查11 if (initialCapacity < 0)12 throw new IllegalArgumentException( "Illegal Capacity: " +13 initialCapacity);14 // 初始化数组15 this.elementData = new Object[initialCapacity];16 this.capacityIncrement = capacityIncrement;17 }18 19 // 使用synchronized关键字锁定方法,保证同一时间内只有一个线程可以操纵该方法20 public synchronized boolean add(E e) {21 modCount++;22 // 扩容检查23 ensureCapacityHelper( elementCount + 1);24 elementData[elementCount ++] = e;25 return true;26 }27 28 private void ensureCapacityHelper(int minCapacity) {29 // 当前元素数量30 int oldCapacity = elementData .length;31 // 是否需要扩容32 if (minCapacity > oldCapacity) {33 Object[] oldData = elementData;34 // 如果自定义了容器扩容递增大小,则按照capacityIncrement进行扩容,否则按两倍进行扩容(*2)35 int newCapacity = (capacityIncrement > 0) ?36 (oldCapacity + capacityIncrement) : (oldCapacity * 2);37 if (newCapacity < minCapacity) {38 newCapacity = minCapacity;39 }40 // 数组copy41 elementData = Arrays.copyOf( elementData, newCapacity);42 }43 }
Vector就简单看到这里,其他方法Stack如果没有调用的话就不进行分析了,不明白的可以去看ArrayList源码解析。
3.peek()——获取栈顶的对象
1 /** 2 * 获取栈顶的对象,但是不删除 3 */ 4 public synchronized E peek() { 5 // 当前容器元素个数 6 int len = size(); 7 8 // 如果没有元素,则直接抛出异常 9 if (len == 0)10 throw new EmptyStackException();11 // 调用elementAt方法取出数组最后一个元素(最后一个元素在栈顶)12 return elementAt(len - 1);13 }14 15 /**16 * 根据index索引取出该位置的元素,这个方法在Vector中17 */18 public synchronized E elementAt(int index) {19 // 越界检查20 if (index >= elementCount ) {21 throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);22 }23 24 // 直接通过数组下标获取元素25 return (E)elementData [index];26 }
4.pop()——弹栈(出栈),获取栈顶的对象,并将该对象从容器中删除
1 /** 2 * 弹栈,获取并删除栈顶的对象 3 */ 4 public synchronized E pop() { 5 // 记录栈顶的对象 6 E obj; 7 // 当前容器元素个数 8 int len = size(); 9 10 // 通过peek()方法获取栈顶对象11 obj = peek();12 // 调用removeElement方法删除栈顶对象13 removeElementAt(len - 1);14 15 // 返回栈顶对象16 return obj;17 }18 19 /**20 * 根据index索引删除元素21 */22 public synchronized void removeElementAt(int index) {23 modCount++;24 // 越界检查25 if (index >= elementCount ) {26 throw new ArrayIndexOutOfBoundsException(index + " >= " +27 elementCount);28 }29 else if (index < 0) {30 throw new ArrayIndexOutOfBoundsException(index);31 }32 // 计算数组元素要移动的个数33 int j = elementCount - index - 1;34 if (j > 0) {35 // 进行数组移动,中间删除了一个,所以将后面的元素往前移动(这里直接移动将index位置元素覆盖掉,就相当于删除了)36 System. arraycopy(elementData, index + 1, elementData, index, j);37 }38 // 容器元素个数减139 elementCount--;40 // 将容器最后一个元素置空(因为删除了一个元素,然后index后面的元素都向前移动了,所以最后一个就没用了 )41 elementData[elementCount ] = null; /* to let gc do its work */42 }
5.push(E item)——压栈(入栈),将对象添加进容器并返回
1 /** 2 * 将对象添加进容器并返回 3 */ 4 public E push(E item) { 5 // 调用addElement将元素添加进容器 6 addElement(item); 7 // 返回该元素 8 return item; 9 }10 11 /**12 * 将元素添加进容器,这个方法在Vector中13 */14 public synchronized void addElement(E obj) {15 modCount++;16 // 扩容检查17 ensureCapacityHelper( elementCount + 1);18 // 将对象放入到数组中,元素个数+119 elementData[elementCount ++] = obj;20 }
6.search(Object o)——返回对象在容器中的位置,栈顶为1
1 /** 2 * 返回对象在容器中的位置,栈顶为1 3 */ 4 public synchronized int search(Object o) { 5 // 从数组中查找元素,从最后一次出现 6 int i = lastIndexOf(o); 7 8 // 因为栈顶算1,所以要用size()-i计算 9 if (i >= 0) {10 return size() - i;11 }12 return -1;13 }
7.empty()——容器是否为空
1 /**2 * 检查容器是否为空3 */4 public boolean empty() {5 return size() == 0;6 }
到这里Stack的方法就分析完成了,由于Stack最终还是基于数组的,理解起来还是很容易的(因为有了ArrayList的基础啦)。
虽然jdk中Stack的源码分析完了,但是这里有必要讨论下,不知道是否发现这里的Stack很奇怪的现象,
(1)Stack为什么是基于数组实现的呢?
我们都知道数组的特点:方便根据下标查询(随机访问),但是内存固定,且扩容效率较低。很容易想到Stack用链表实现最合适的。
(2)Stack为什么是继承Vector的?
继承也就意味着Stack继承了Vector的方法,这使得Stack有点不伦不类的感觉,既是List又是Stack。如果非要继承Vector合理的做法应该是什么:Stack不继承Vector,而只是在自身有一个Vector的引用,聚合对不对?
唯一的解释呢,就是Stack是jdk1.0出来的,那个时候jdk中的容器还没有ArrayList、LinkedList等只有Vector,既然已经有了Vector且能实现Stack的功能,那么就干吧。。。
既然用链表实现Stack是比较理想的,那么我们就来尝试一下吧:
1 import java.util.LinkedList; 2 3 public class LinkedStack<E> { 4 5 private LinkedList<E> linked ; 6 7 public LinkedStack() { 8 this.linked = new LinkedList<E>(); 9 }10 11 public E push(E item) {12 this.linked .addFirst(item);13 return item;14 }15 16 public E pop() {17 if (this.linked.isEmpty()) {18 return null;19 }20 return this.linked.removeFirst();21 }22 23 public E peek() {24 if (this.linked.isEmpty()) {25 return null;26 }27 return this.linked.getFirst();28 }29 30 public int search(E item) {31 int i = this.linked.indexOf(item);32 return i + 1;33 }34 35 public boolean empty() {36 return this.linked.isEmpty();37 }38 }
看完后,你说我cha,为什么这么简单,就这么点代码。因为这里使用的LinkedList实现的Stack,记得在LinkedList中说过,LinkedList实现了Deque接口使得它既可以作为栈(先进后出),又可以作为队列(先进先出)。那么什么是队列呢?Queue见吧!
Stack&Vector 完!
参见: