本文对Java中的Stack集合的源码进行了深度解析,包括各种方法的底层实现,并且给出了Stack的使用建议。
文章目录
1 Stack的概述
public class Stack< E >
extends Vector< E >
Stack,来自于JDK1.0 的古老集合类,底层是数组结构,元素可重复,有序(存放顺序),支持下标索引访问,允许null元素。
Stack类继承了Vector,所有方法的实现都是同步的,方法采用了synchronized修饰,数据安全,效率低!。
Stack针对Vector扩展了五个API方法,主要被用来实现为先进后出(FILO)的栈。
2 Stack的源码解析
由于Stack继承了Vector类,因此继承了Vector类的属性和方法,同时他扩展了Vector类的方法,即新增了五个方法,用于实现先进后出的栈结构。关于Vector集合的介绍,可以看这篇文章:Java的Vector集合源码深度解析以及应用介绍。
Stack的将底层数组的尾部看成了栈顶,因此入栈时将元素加在尾部,出栈时直接移除尾部的元素,比较简单,不会导致大量元素位置移动!
2.1 构造器
仅有一个自己的构造器!用于创建一个空堆栈。
public Stack() {
}
2.2 API方法
这里讲解相比于Vector新增的将用于实现栈的五个方法!
2.2.1. public E push(E item)
压栈:将新元素放在栈顶的位置。新加入的元素把原来的元素往下“压”,默认最后加入的元素在栈顶。
/**
* 压栈(入栈),在内部实现是将元素放在内部数组的末尾
* @param item 添加的元素
* @return 被添加的元素
*/
public E push(E item) {
//调用了父类的addElement方法,将元素放在内部数组的末尾
addElement(item);
//返回被添加的元素
return item;
}
2.2.2 public E peek()
获得栈顶元素,但是不移除。如果栈为空,将抛出EmptyStackException异常。
/**
* 获取栈顶元素但不移除
* @return 栈顶元素
*/
public synchronized E peek() {
//获取元素数量
int len = size();
//如果数量等于0则说明是空栈,抛出异常
if (len == 0)
throw new EmptyStackException();
//返回内部数组的尾部元素
return elementAt(len - 1);
}
2.2.3 public E pop()
获得栈顶,并移除(弹栈)。如果栈为空,将抛出EmptyStackException异常。
/**
* 获取栈顶元素并不移除
* @return 栈顶元素
*/
public synchronized E pop() {
E obj;
//获取元素数量
int len = size();
//获取栈顶元素但不移除
obj = peek();
//移除底层数组尾部元素
removeElementAt(len - 1);
//返回被出栈的元素
return obj;
}
2.2.4 public boolean empty()
判断栈容器是否为空栈。
public boolean empty() {
//很简单,直接看元素数量是否等于0
return size() == 0;
}
2.2.5 public int search(Object o)
搜索o在栈空间当中的位置:以1为基础,从栈顶开始计算,返回值为-1时表示此对象不在堆栈中。底层使用 equals 方法比较 o 与堆栈中的项。
/**
* 搜索o在栈空间当中的位置:以1为基础,从栈顶开始计算,返回值为-1时表示此对象不在堆栈中。底层使用 equals 方法比较 o 与堆栈中的项。
* @param o 搜索的元素
* @return 位置
*/
public synchronized int search(Object o) {
//从尾部倒序向头部搜索索引位置,返回第一个被找到的元素的索引,没找到返回-1
int i = lastIndexOf(o);
//如果位置i大于等于0,则返回size-i;
//即如果size=5,i=4,那么该元素在"栈"结构中的位置因该是1,即以1为基础,栈顶元素位置就是1
if (i >= 0) {
return size() - i;
}
//没找到就返回-1
return -1;
}
2.2.6 案例
public class StackDemo01 {
public static void main(String[] args) {
Stack<String> s = new Stack<>();
s.push("a");
s.push("b");
s.push("c");
s.push("d");
while (!s.empty()) {
String pop = s.pop();
System.out.print(pop + " ");
}
}
}
3 总结
Stack类的实现还是很简单的,相比父类Vector就是多了五个方法。但是由于来自于JDK1.0,那时候的集合都很原始并且使用Synchronized修饰,性能可能不是很好,并且底层实现比较粗糙!
在JDK1.6的时候,添加了集合类ArrayDeque,该类实现了Deque接口,即作为一个“双端队列”,可以被用来实现Stack(栈)或者Queue(队列)。
ArrayDeque内部是采用可变数组实现的双端队列,采用两个外部引用来保持队列头结点和尾节点的访问,同时删除队列头尾元素时不会移动其他元素,而是移动引用的位置,即形成一个环形队列,能够复用数组空间(允许队头索引比队尾索引值更大),相比于另外一个使用链表实现的双端队列LinkedList综合效率更好(LinkedList介绍:Java的LinkedList集合源码深度解析以及应用介绍)!同时,如果用于实现栈,那么相比于Stack的综合效率同样更好!不过ArrayDeque不支持null元素,并且不是线程安全的!
因此,JDK推荐我们一般使用ArrayDeque来替代Stack实现栈!
同时如果你真的想了解JVM的栈空间的工作过程,可以看看这篇文章:Java的JVM运行时栈结构和方法调用详解,看完你会发现为什么上面的集合可以用来模拟“栈空间”了,因为方法在栈空间中的调用也是“先进后出”的!
我们后续将会介绍的更多集合,比如TreeMap、HashMap,LinkedHashMap、HashSet、TreeSet、ArrayDeque等基本集合以及JUC包中的高级并发集合。如果想学习集合源码的关注我的专栏更新!
如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!