简体   繁体   English

为什么我应该使用 Deque 而不是 Stack?

[英]Why should I use Deque over Stack?

I need a Stack data structure for my use case.我的用例需要一个Stack数据结构。 I should be able to push items into the data structure and I only want to retrieve the last item from the Stack.我应该能够将项目推送到数据结构中,我只想从堆栈中检索最后一个项目。 The JavaDoc for Stack says: Stack 的 JavaDoc说:

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 接口及其实现提供了一组更完整和一致的 LIFO 堆栈操作,应优先使用此 class。例如:

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

I definitely do not want synchronized behavior here as I will be using this datastructure local to a method.我绝对不希望这里有同步行为,因为我将使用方法本地的这个数据结构。 Apart from this why should I prefer Deque over Stack here?除此之外,为什么我在这里更喜欢Deque而不是Stack

PS: The javadoc from Deque says: PS:来自 Deque 的 javadoc 说:

Deques can also be used as LIFO (Last-In-First-Out) stacks.双端队列也可以用作 LIFO(后进先出)堆栈。 This interface should be used in preference to the legacy Stack class.应优先使用此接口,而不是旧版 Stack class。

For one thing, it's more sensible in terms of inheritance.一方面,它在继承方面更明智。 The fact that Stack extends Vector is really strange, in my view.在我看来, Stack扩展Vector的事实真的很奇怪。 Early in Java, inheritance was overused IMO - Properties being another example.在 Java 早期,IMO 过度使用了继承—— Properties是另一个例子。

For me, the crucial word in the docs you quoted is consistent .对我来说,您引用的文档中的关键词是一致的。 Deque exposes a set of operations which is all about being able to fetch/add/remove items from the start or end of a collection, iterate etc - and that's it. Deque公开了一组操作,这些操作都是关于能够从集合的开头或结尾获取/添加/删除项目、迭代等 - 就是这样。 There's deliberately no way to access an element by position, which Stack exposes because it's a subclass of Vector .故意无法按位置访问元素,因为它是Vector的子类,所以Stack暴露了它。

Oh, and also Stack has no interface, so if you know you need Stack operations you end up committing to a specific concrete class, which isn't usually a good idea.哦,而且Stack没有接口,所以如果你知道你需要Stack操作,你最终会提交到一个特定的具体类,这通常不是一个好主意。

Also as pointed out in the comments, Stack and Deque have reverse iteration orders:同样正如评论中指出的那样, 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

which is also explained in the JavaDocs for Deque.iterator() :这也在Deque.iterator()的 JavaDocs 中进行了解释:

Returns an iterator over the elements in this deque in proper sequence.以正确的顺序返回此双端队列中元素的迭代器。 The elements will be returned in order from first (head) to last (tail).元素将按从第一个(头)到最后一个(尾)的顺序返回。

Here are a few reasons why Deque is better than Stack:以下是 Deque 优于 Stack 的几个原因:

Object oriented design - Inheritance, abstraction, classes and interfaces: Stack is a class, Deque is an interface.面向对象设计——继承、抽象、类和接口:Stack是一个类,Deque是一个接口。 Only one class can be extended, whereas any number of interfaces can be implemented by a single class in Java (multiple inheritance of type).只能扩展一个类,而 Java 中的单个类可以实现任意数量的接口(类型的多重继承)。 Using the Deque interface removes the dependency on the concrete Stack class and its ancestors and gives you more flexibility, eg the freedom to extend a different class or swap out different implementations of Deque (like LinkedList, ArrayDeque).使用 Deque 接口消除了对具体 Stack 类及其祖先的依赖,并为您提供了更大的灵活性,例如,可以自由扩展不同的类或交换 Deque 的不同实现(如 LinkedList、ArrayDeque)。

Inconsistency: Stack extends the Vector class, which allows you to access element by index.不一致:Stack 扩展了 Vector 类,它允许您按索引访问元素。 This is inconsistent with what a Stack should actually do, which is why the Deque interface is preferred (it does not allow such operations)--its allowed operations are consistent with what a FIFO or LIFO data structure should allow.这与堆栈实际应该做的事情不一致,这就是为什么首选 Deque 接口(它不允许这样的操作)——它允许的操作与 FIFO 或 LIFO 数据结构应该允许的操作一致。

Performance: The Vector class that Stack extends is basically the "thread-safe" version of an ArrayList.性能:Stack 扩展的 Vector 类基本上是 ArrayList 的“线程安全”版本。 The synchronizations can potentially cause a significant performance hit to your application.同步可能会对您的应用程序造成重大的性能影响。 Also, extending other classes with unneeded functionality (as mentioned in #2) bloat your objects, potentially costing a lot of extra memory and performance overhead.此外,使用不需要的功能(如 #2 中提到的)扩展其他类会使您的对象膨胀,可能会花费大量额外的内存和性能开销。

One more reason to use Deque over Stack is Deque has the ability to use streams convert to list with keeping LIFO concept applied while Stack does not.使用Deque over Stack的另一个原因是Deque能够使用流转换为列表,同时保持 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]

Here is my interpretation of inconsistency mentioned in the description of Stack class.这是我对 Stack 类描述中提到的不一致的解释。

If you look at General-purpose Implementations here - you'll see there is a consistent approach to implementation of set, map and list.如果您在此处查看通用实现 - 您会看到集合、映射和列表的实现方法一致。

  • For set and map we have 2 standard implementations with hash maps and trees.对于 set 和 map,我们有 2 个带有哈希映射和树的标准实现。 The first one is most used and the second one is used when we need an ordered structure (and it also implements its own interface - SortedSet or SortedMap).第一个是最常用的,第二个在我们需要有序结构时使用(它也实现了自己的接口 - SortedSet 或 SortedMap)。

  • We may use the preferred style of declaring like Set<String> set = new HashSet<String>();我们可以使用首选的声明风格,例如Set<String> set = new HashSet<String>(); see reasons here . 在这里查看原因。

But Stack class: 1) don't have its own interface;但是 Stack 类: 1) 没有自己的接口; 2) is a subclass of Vector class - which is based on resizable array; 2) 是 Vector 类的子类 - 基于可调整大小的数组; so where is linked list implementation of stack?那么栈的链表实现在哪里呢?

In Deque interface we don't have such problems including two implementations (resizable array - ArrayDeque; linked list - LinkedList).在 Deque 接口中,我们没有这样的问题,包括两个实现(可调整大小的数组 - ArrayDeque;链表 - LinkedList)。

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

I need a Stack data structure for my use case.我的用例需要一个Stack数据结构。 I should be able to push items into the data structure and I only want to retrieve the last item from the Stack.我应该能够将项目推入数据结构,我只想从堆栈中检索最后一个项目。 The JavaDoc for Stack says : 堆栈JavaDoc说:

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. Deque 接口及其实现提供了一组更完整和一致的 LIFO 堆栈操作,应优先使用此类。 For example:例如:

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

I definitely do not want synchronized behavior here as I will be using this datastructure local to a method .我绝对不想要这里的同步行为,因为我将在方法中使用这个数据结构。 Apart from this why should I prefer Deque over Stack here ?除此之外,为什么我更喜欢Deque不是Stack呢?

PS: The javadoc from Deque says : PS:来自 Deque 的 javadoc 说:

Deques can also be used as LIFO (Last-In-First-Out) stacks.双端队列也可以用作 LIFO(后进先出)堆栈。 This interface should be used in preference to the legacy Stack class.应优先使用此接口而不是遗留 Stack 类。

Performance might be a reason.性能可能是一个原因。 An algorithm I used went down from 7.6 minutes to 1.5 minutes by just replacing Stack with Deque.我使用的算法只需用 Deque 替换 Stack,从 7.6 分钟缩短到 1.5 分钟。

There are several reason that Deque is better than stack for implementation: Deque 在实现方面优于堆栈有几个原因:

  1. Deque is an interface and stack is a class: In class creation it is better to implement an interface than extend a class because after extending you cannot extend another class, you can only implement an interface in the other hand when you implement an interface you can extend a class and also implement another interfaces.双端队列是接口,栈是类:在类创建中,实现接口比扩展类更好,因为扩展后你不能扩展另一个类,你只能实现一个接口,而当你实现一个接口时,你可以扩展一个类并实现另一个接口。

  2. Synchronization: Because stack class is a subclass of the vector class which is asynchronized therefor stack is too But Deque is not.同步:因为栈类是向量类的子类,所以栈也是异步的,但Deque不是。 So if there is no need for synchronization then for better performance we should use Deque.因此,如果不需要同步,那么为了获得更好的性能,我们应该使用 Deque。

  3. Deque's iterator works the way we expect for a stack: iteration in a stack is bottom to top (FIFO (First In First Out)). Deque 的迭代器按照我们期望的堆栈方式工作:堆栈中的迭代是自下而上的(FIFO(先进先出))。 But iteration in a Deque is top to bottom (LIFO (Last In First Out)).但是 Deque 中的迭代是从上到下的(LIFO(后进先出))。

  4. Stack isn't truly LIFO: We know that stack is a subclass of the vector class so we can access to elements by their indexes which is against LIFO contract. Stack 不是真正的 LIFO:我们知道 stack 是 vector 类的子类,因此我们可以通过元素的索引访问元素,这违反了 LIFO 协定。

If for some reason you want to switch from Stack to Deque, but want to preserve the same order when converting to an ArrayList, you can use Deque.descendingIterator() .如果出于某种原因你想从 Stack 切换到 Deque,但想在转换为 ArrayList 时保留相同的顺序,你可以使用Deque.descendingIterator()

However ArrayList does not have a constructor accepting an Iterator, so you might want to combine it with a library such as Guava (or Apache Commons):但是 ArrayList 没有接受迭代器的构造函数,因此您可能希望将它与 Guava(或 Apache Commons)等库结合使用:

Lists.newArrayList(deque.descendingIterator()) // Guava

Or Java 8:或 Java 8:

List<Integer> list = new ArrayList<>();
deque.descendingIterator().forEachRemaining(list::add);

Deque is used is situation where you want to retrieve elements from both head and tail. 当您要从头和尾检索元素时,使用了双端队列。 If you want a simple stack there is no need to go for a deque. 如果您想要一个简单的堆栈,则无需进行双端队列。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM