Java中栈和队列的使用及区别

1、Stack(栈)

  在java8中,Stack的官方文档介绍如下:

  public class Stack<E> extends Vector<E>
  The Stack class represents a last-in-first-out (LIFO) stack of objects.It extends class Vector with five operations that allow a vector to be treated as a stack.
 The usual push and pop operations are provided, as well as a method to peek at the top item on the stack, a method to test for whether the stack is empty, and a method to search the stack for an item and discover how far it is from the top.
 When a stack is first created, it contains no items.
 ​
  A more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class. For example:
 ​
  Deque<Integer> stack = new ArrayDeque<Integer>();

  大致意思为:Stack类表示对象的后进先出(LIFO:先进后出)。它使用五个操作扩展了Vector类,这些操作允许将矢量视为栈。提供了通常的推入(push)和弹出(pop)操作,以及一种查看栈顶部的方法(peek),一种用于测试堆栈是否为空的方法(empty)以及一种用于在栈中搜索元素并发现其和顶部top距离的方法(search)。从顶部开始。 首次创建堆栈时,它不包含任何项目。

  除此之外Deque接口及其实现提供了一组更完整和一致的LIFO堆栈操作,应优先使用此类。例如:

     Deque<Integer> stack = new ArrayDeque<Integer>();

  从上面的信息我们可以得出Stack有5个常用的方法

Modifier and Type Method and Description
boolean empty() :测试栈是否为空
E peek() :在不将其从栈中移除的情况下,查看该栈顶部的对象。在栈为空的时候会报EmptyStackException
E pop() :删除此栈顶部的对象,并将该对象作为此函数的值返回。在栈为空的时候会报EmptyStackException
E push(E item) :将一个项目推送到此堆栈的顶部。
int search(Object o) :返回对象在此栈上的从1开始的位置。对象不存在则会返回-1

  我们可以看到Satck的功能基本上已经满足我们的要求了,可是在文档上依然推荐我们使用Deque双端队列的实现类来完成Satck的功能,这是因为java中Stack类设计的有一些问题,因为我们可以看到Satck是继承自 Vector,但是依照程序设计的一个原则多组合少继承来看,这个设计是不合理的,应该是Vector组成Satck比较好,然后java的官方也发现了这个问题,但是后面想更改的时候时间就比较晚了,所以才在文档里面建议我们使用双端队列来模拟栈比较好。

  总结:Deque接口和它的实现提供了比Stack更完整的方法,以及也是FILO的原则,应优先使用此类。

 

2、Queue(单向队列)

  在java中Queue是一个接口,在文档当中有对其的描述如下:

  Queue除了基本的“收集”操作外,队列还提供其他插入,提取和检查操作。这些操作中的每一种都以两种形式存在:一种在操作失败时引发异常,另一种返回一个特殊值(根据操作为null或false)。插入操作的后一种形式是专为与容量受限的Queue实现一起使用而设计的。在大多数实现中,插入操作不会失败。

  下面这个表格就对应着一个发生异常,一个返回特殊值:

  Throws exception Returns special value
Insert add(e) offer(e)
Remove remove() poll()
Examine element() peek()

  对应的方法介绍:

Modifier and Type Method and Description
boolean add(E e) :如果可以立即将指定的元素插入此队列,而不会违反容量限制,则在成功时返回true,如果当前没有可用空间,则抛出IllegalStateException。
E element() :检索但不删除此队列的头。
boolean offer(E e) :如果可以在不违反容量限制的情况下立即将指定的元素插入此队列。
E peek() :检索但不删除此队列的头部,如果此队列为空,则返回null。
E poll() :检索并删除此队列的头部,如果此队列为空,则返回null。
E remove() :检索并删除此队列的头。

 

3、Deque(双端队列/栈)

  Deque是双端队列的接口,也是我们使用最多的队列,既可以当作栈也可以当作队列使用。

  Deque是支持在两端插入和删除元素的线性集合,双端队列是“双端队列”(double ended queue)的缩写。大多数Deque对它们可能包含的元素数量没有固定的限制,但是此接口支持容量受限的双端队列以及没有固定大小限制的双端队列。此接口定义访问双端队列两端的元素的方,提供了用于插入,删除和检查元素的方法。这些方法中的每一种都以两种形式存在:一种在操作失败时引发异常,另一种返回一个特殊值(根据操作为null或false)。插入操作的后一种形式是专为容量受限的Deque实现而设计的。在大多数实现中,插入操作不会失败。

  类似于队列,它也有一个表格:

  First Element (Head)

  Throws exception Returns special value
Insert addFirst(e) offerFirst(e)
Remove removeFirst() pollFirst()
Examine getFirst() peekFirst()

  Last Element (Tail)

  Throws exception Returns special value
Insert addLast(e) offerLast(e)
Remove removeLast() pollLast()
Examine getLast() peekLast()

  因为该接口扩展了Queue接口,当双端队列用作队列时,将导致FIFO(先进先出)行为。元素在双端队列的末尾添加,并从开头删除。从Queue接口继承的方法与Deque方法完全等效,如下表所示:

Queue Method Equivalent Deque Method
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()

  双端队列也可以用作LIFO(后进先出、先进后出)堆栈,此接口应优先于旧版Stack类使用。当双端队列用作堆栈时,元素从双端队列的开头被压入并弹出。堆栈方法与Deque方法完全等效,如下表所示:

Stack Method Equivalent Deque Method
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst()

  总结来说,使用Deque作为队列时主要使用队列中的表对应提供的方法,而使用Deque作为栈时主要使用栈对应的表提供的方法。

  因为Deque继承自Queue,所以Queue的方法本身在Deque中是起作用的,但是除此之外Deque中对应栈的方法如:push、pop也是有的,并且功能也是刚好对应。

   public void push(E e) {
    addFirst(e);
   }
 ​
   public E pop() {
    return removeFirst();
   }

  所以,总结来说,使用栈用Deque实现是完全没有问题的。

  另外需要说明Deque的实现类有许多,但是我们平时用不考虑多线程,使用ArrayDeque、LinkedList就足够了。

 

4、PriorityQueue(优先级队列/堆)

  PriorityQueue是基于优先级堆的*优先级队列。优先级队列的元素根据其自然顺序或在队列构造时提供的Comparator进行排序,具体取决于所使用的构造函数。优先级队列不允许空元素。依赖自然顺序的优先级队列也不允许插入不可比较的对象,这样做可能会导致ClassCastException

  简单来说,PriorityQueue就是一个优先级队列,在我们需要堆的时候可以使用PriorityQueue当作堆进行使用,因为PriorityQueue继承自AbstractQueue,而AbstractQueue实现Queue,所以PriorityQueue的方法和Queue差不多,使用起来也比较方便。

 

5、队列与栈的区别

5.1 数据的插入、删除

5.1.1 栈

  是一种特殊的线性表,它只能在一段进行插入和删除操作,就好像是一个井一样。进行数据插入和删除的一端类似于井口,称为栈顶。而井也是有底部的,栈无法进行插入删除操作的一端就被称为栈底。 栈中的数据好似往井里加水或抽水,加水永远是加的离井口最近的水,同样,抽水也是抽的离井口最近的水,这种特性也就是先进后出(FILO:First Input Last Output)。 加水的操作就是进栈/压栈/入栈,入的数据在栈的最顶端。 抽水的操作就是出栈,出的数据也在栈的顶端。

Java中栈和队列的使用及区别

5.1.2 队列

  队列只允许在一端进行插入操作,另一端进行删除操作。就像是排队一样,只能从后面排队,在队列的前面处理业务。而先排队的人可以先进行业务处理,这也就是队列的先进先出(FIFO:First Input First Output)原则。 队列中的数据就是正在排队的人,入队列也就是加入排队,而加入的那一边称为队尾,出队列也就是业务处理完成,离开排队的地方,这一边则被称之为队头Java中栈和队列的使用及区别

5.2 遍历速度

  :由于栈是先进后出,且只能在栈顶取出数据,而最先放入栈的数据最后才能被遍历到,栈在遍历时一般需要另外开辟空间来保证数据在遍历时不会被打乱。

  队列:队列能基于地址指针进行遍历,而且可以从队头/队尾开始遍历(不能两边同时遍历),不需要另外开辟空间来保证数据的顺序,不影响数据结构,因此速度要更快。

5.3 适用场景不同

  :具有记忆能力,使用于括号求解、表达式转换、函数递归和调用的实现、深度优先搜索遍历、浏览器后退功能等,需要记忆原来数据内容的场景。

  队列:可以进行有顺序的处理,如计算机系统中各种资源的管理、消息缓冲器的管理、广度优先搜索等场景。

 

6、总结

在不考虑多线程的情况下

  • 使用栈就是使用Deque的实现类

  • 使用队列就使用Deque的实现类

  • 使用堆就使用PriorityQueue。

 

参考链接

上一篇:C++学习日记 容器deque、stack、list、set


下一篇:STL源码剖析