栈和 队列用来存储一组有序的值,二者很相似,栈是一种先进后出的结构,也就是最后保存的元素最先被访问;队列是一种先进先出的结构,也就是最先保存的元素最先被访问。
1.Stack
栈通常是指先进后出的容器。有时候栈也被称为叠加栈,因为最后压入的元素,第一个弹出栈,有图有真相:
Stack 框架图:
可以看出 Stack 继承了 Vector,实现了 List 接口,也是 Collection 大家族中的一员。下面瞅瞅 Stack 的常规操作:
public static void main(String[] args) {
//创建 Stack 对象
Stack<String> stack = new Stack<>();
String str = "this is a stack";
for (String s : str.split(" ")) {
//将值放入栈中
stack.push(s);
}
System.out.println(stack);
System.out.println("栈的大小:" + stack.size());
System.out.println("栈顶的值:" + stack.peek());
System.out.println("删除并返回栈顶的值:" + stack.pop());
System.out.println("删除栈顶后的栈:" + stack);
//打印结果和上面相反
while (!stack.empty()){
System.out.print(stack.pop() + " ");
}
}
打印结果:
[this, is, a, stack]
栈的大小:4
栈顶的值:stack
删除并返回栈顶的值:stack
删除栈顶后的栈:[this, is, a]
a is this
从代码和打印结果可以看出,Stack 的操作和 LinkedLIst 的操作非常相似:
- push(E item) 方法,将值压入栈的顶部;
- size() 方法,得到栈的大小;
- peek() 方法,返回栈顶元素,但是不删除该元素;
- pop() 方法,删除并返回栈顶元素;
- empty() 方法,判断栈是否为空,如果为空返回 true。
2.Queue
队列是一个典型的先进先出的容器,即从容器的一端放入事物然后从另一端取出,并且事物放入容器的顺序与取出的顺序是相同的。队列在并发编程中尤为重要,因为它可以安全地将对象从一个任务传输给另一个任务。
Queue 框架图:
通过框架图可以看到 LinkedList 实现了 Queue 接口并且提供了方法以支持队列的行为。简单示例:
public static void main(String[] args) {
//通过 LinkedList 向上转型创建 Queue 对象
Queue<Integer> queue = new LinkedList<>();
Random random = new Random();
for (int i = 0; i < 10; i++) {
//向队列末尾添加元素
queue.offer(random.nextInt(10));
//queue.add(random.nextInt(10));
}
System.out.println("队列: " + queue);
System.out.println("队列头:" + queue.peek());
System.out.println("队列头:" + queue.element());
System.out.println("删除并返回队列头:" + queue.poll());
System.out.println("删除并返回队列头:" + queue.remove());
}
打印结果:
队列: [2, 6, 4, 5, 3, 5, 9, 6, 2, 6]
队列头:2
队列头:2
删除并返回队列头:2
删除并返回队列头:6
Queue 的使用似曾相识,这些方法在文章 List 简介 中已经介绍过,Queue 接口窄化了对 LinkedList 的方法的访问权限,只对外暴露了以下方法:
- offer(E e) 方法和 add(E e) 方法一样,都是向队列的末尾添加一个元素;
- peek() 方法 和 element() 方法都是在不删除的情况下返回队列的头元素,但是 peek() 方法在队列为空时返回 null,而 element() 方法则会抛出异常;
- poll() 方法和 remove() 方法都是删除并返回队列头元素,但是 poll() 方法在队列为空的时候返回 null,而 remove() 方法则会抛出异常。
3.PriorityQueue
先进先出描述了最典型的队列规则,声明的是下一个弹出队列的元素应该是等待时间最长的元素。优先级队列声明下一个弹出元素是最需要的元素,也就是具有最高的优先级,当在 PriorityQueue 中添加一个对象时,这个对象会在队列中被排序,规则是对象在队列中的自然顺序,如果需要修改这个顺序,那么可以通过提供自己的 Comparator 来实现。
/**
* 打印 queue
* @param queue
*/
public static void printQueue(Queue queue){
while(!queue.isEmpty()){
System.out.print(queue.poll() + ",");
}
}
public static void main(String[] args) {
//创建一个整数集合
List<Integer> list = Arrays.asList(3,6,1,8,5,1,7,9,0,4);
//创建 PriorityQueue 对象
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.addAll(list);
System.out.println("默认排序:");
//打印
printQueue(priorityQueue);
System.out.println();
//创建倒序的 PriorityQueue 对象
priorityQueue = new PriorityQueue<>(list.size(), new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
priorityQueue.addAll(list);
System.out.println("倒序:");
//打印
printQueue(priorityQueue);
}
打印结果:
默认排序:
0,1,1,3,4,5,6,7,8,9,
倒序:
9,8,7,6,5,4,3,1,1,0,
当使用 PriorityQueue 的无参构造器创建 PriorityQueue 优先级队列的时候,队列中元素的顺序默认是自然排序,如果需要制定排序规则,就需要调用 PriorityQueue 的有参构造器,然后自定义 Comparator 对象重写 compareTo() 方法。而且从打印结果来看,元素的重复是允许的。
中间还有一个小插曲,当使用 PriorityQueue 的 toString() 方法去打印的时候,发现顺序居然不对,最后使用了上述代码中的遍历的方式才打印出理想中的结果。PriorityQueue 的逻辑结构是一棵完全二叉树,存储结构其实就是一个数组,逻辑层次遍历的结果刚好是一个数组。
小结
本章介绍了 Java 的栈和队列的概念和基本使用,补充了优先级队列的相关内容,打印优先级队列的时候需要使用遍历的方式。