为什么要在堆栈上使用Deque?


157

Stack我的用例需要一个数据结构。我应该能够将项目推送到数据结构中,而我只想从堆栈中检索最后一个项目。该堆栈的JavaDoc说:

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

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

我绝对不希望这里出现同步行为,因为我将在方法本地使用此数据结构。除了这个,我为什么还要DequeStack这里呢?

PS:Deque的Javadoc说:

双端队列也可以用作LIFO(后进先出)堆栈。此接口应优先于旧版Stack类使用。


1
它提供了更多的内置方法,或者说是它们的“更完整和一致的集合”,这将减少如果您利用它们时必须编写的代码量?

Answers:


190

一方面,在继承方面更明智。在我看来,Stack扩展的事实Vector确实很奇怪。在Java的早期,继承被IMO过度使用-这Properties是另一个例子。

对我而言,您引用的文档中的关键词是一致的Deque公开了一组操作,这些操作都与从集合的开头或结尾获取,添加/删除项,进行迭代等有关-仅此而已。故意没有办法按位置访问元素,这是因为它是的子类而Stack暴露出来。Vector

哦,并且也Stack没有接口,所以如果您知道需要Stack操作,最终将提交一个特定的具体类,通常这不是一个好主意。

也如注释中指出的那样,StackDeque具有反向迭代顺序:

Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(new ArrayList<>(stack)); // prints 1, 2, 3


Deque<Integer> deque = new ArrayDeque<>();
deque.push(1);
deque.push(2);
deque.push(3);
System.out.println(new ArrayList<>(deque)); // prints 3, 2, 1

JavaDocs for Deque.iterator()也对此进行了说明:

以适当的顺序返回此双端队列中的元素的迭代器。元素将按照从头(头)到后(尾)的顺序返回。


10
ArrayDeque的javadoc说:“此类用作堆栈时,可能比Stack快,而用作队列时,则比LinkedList快。” ..如何指定我打算将其用作堆栈还是队列?
极客

23
@Geek:你不知道。关键是,如果您要排队,可以使用LinkedList,但是ArrayDequeue(通常)会更快。如果您想要堆栈行为,可以使用,Stack但是ArrayDeque(通常)会更快。
乔恩·斯基特

7
难道就抽象而言,它不那么明智吗?我的意思是,由于Stack存在rep暴露问题,这两种解决方案在抽象方面都不是很好,但是如果我想要堆栈数据结构,我希望能够调用诸如push,pop和peek之类的方法,而不必调用用堆栈的另一端。
PeteyPabPro 2014年

4
@JonSkeet也Stack的迭代器是错误的,因为它是从下到上而不是从上到下进行迭代。stackoverflow.com/questions/16992758/…–
Pavel

1
@PeteyPabPro:你是对的。将出队作为堆栈使用仍然允许非LIFO用作堆栈的继承的Vector方法。问题和解决方案(与封装)的说明可以在这里找到:baddotrobot.com/blog/2013/01/10/stack-vs-deque
RICS

4

这是我对Stack类的描述中提到的不一致的解释。

如果您在这里查看通用实现-您将看到有一种一致的方法来实现集合,映射和列表。

  • 对于set和map,我们有2个带有哈希图和树的标准实现。第一个最常用,第二个在需要有序结构时使用(它也实现了自己的接口-SortedSet或SortedMap)。

  • 我们可以使用首选的声明方式,例如在此处Set<String> set = new HashSet<String>();查看原因。

但是Stack类:1)没有自己的接口;2)是Vector类的子类-基于可调整大小的数组;那么堆栈的链表实现在哪里?

在Deque接口中,我们没有这样的问题,包括两个实现(可调整大小的数组-ArrayDeque;链接列表-LinkedList)。


2

在Stack上使用Dequeue的另一个原因是Dequeue能够使用流转换为列表的流,并保持应用LIFO概念,而Stack不应用。

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

stack.push(1);//1 is the top
deque.push(1)//1 is the top
stack.push(2);//2 is the top
deque.push(2);//2 is the top

List<Integer> list1 = stack.stream().collect(Collectors.toList());//[1,2]

List<Integer> list2 = deque.stream().collect(Collectors.toList());//[2,1]

2

以下是Deque优于Stack的一些原因:

面向对象的设计-继承,抽象,类和接口:堆栈是一个类,而双端队列是一个接口。只能扩展一个类,而Java中的单个类可以实现任意数量的接口(类型的多重继承)。使用Deque接口消除了对具体Stack类及其祖先的依赖,并为您提供了更大的灵活性,例如,可以自由扩展其他类或替换Deque的不同实现(如LinkedList,ArrayDeque)。

不一致:堆栈扩展了Vector类,该类允许您按索引访问元素。这与Stack实际应该执行的操作不一致,这就是为什么首选Deque接口(它不允许此类操作)的原因-Deque接口允许的操作与FIFO或LIFO数据结构应允许的操作一致。

性能:Stack扩展的Vector类基本上是ArrayList的“线程安全”版本。同步可能会严重影响您的应用程序性能。另外,使用不必要的功能扩展其他类(如#2中所述)会使对象膨胀,从而可能会花费大量额外的内存和性能开销。


-1

对我来说,缺少这一点:堆栈是线程安全的,因为它是从Vector派生的,而大多数双端队列的实现则不是,因此如果仅在单个线程中使用它,则速度更快。


grep“除此以外”
Pacerier

-1

性能可能是一个原因。通过使用Deque替换Stack,我使用的算法从7.6分钟缩短到1.5分钟。


grep“除此以外”
Pacerier

-6

如果想要从头和尾同时检索元素,则使用双端队列。如果您想要简单的堆栈,则无需进行双端队列。


1
请参阅问题的最后一段。javadoc说应该使用Deques,我想知道为什么吗?
极客

2
首选双端队列,因为您不能访问中间的元素。它是双端的,所以可以是FILO for FIFO。
Thufir 2013年

我认为他们打算说的是该类支持Stack操作作为显式方法。请参阅其文档和方法。它对实现堆栈有明确的支持。
阿比舍克·南德冈卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.