ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

我绝对不想在这里同步行为,因为我将在方法本地使用这个数据结构。除此之外,为什么我应该更喜欢 Deque 而不是 Stack

PS:来自 Deque 的 javadoc 说:

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

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

B
Behrang

一方面,它在继承方面更明智。在我看来,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

这也在 Deque.iterator() 的 JavaDocs 中进行了解释:

以正确的顺序返回此双端队列中元素的迭代器。元素将按从第一个(头)到最后一个(尾)的顺序返回。


ArrayDeque 的 javadoc 说“这个类在用作堆栈时可能比 Stack 快,而在用作队列时比 LinkedList 快。” ..如何指定我打算将其用作堆栈还是队列?
@Geek:你没有。关键是,如果您想要排队行为,您可以使用 LinkedList,但 ArrayDequeue(通常)会更快。如果您想要堆栈行为,您可以使用 Stack,但 ArrayDeque(通常)会更快。
但是,在抽象方面不是不太明智吗?我的意思是,这两种解决方案在抽象方面都不是很好,因为 Stack 存在代表暴露问题,但是如果我想要一个堆栈数据结构,我希望能够调用诸如 push、pop 和 peek 之类的方法,而不是与堆栈的另一端有关的事情。
@JonSkeet Stack 的迭代器也是错误的,因为它从下到上而不是从上到下迭代。 stackoverflow.com/questions/16992758/…
@PeteyPabPro:你是对的。使用 Dequeue 作为堆栈仍然允许非 LIFO 使用作为 Stack 的继承 Vector 方法。可以在此处找到问题的解释和解决方案(带封装):baddotrobot.com/blog/2013/01/10/stack-vs-deque
A
Arpit Bhargava

以下是 Deque 优于 Stack 的几个原因:

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

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

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


这是一个很好的利基解释。一个简短的采访材料无疑。谢谢。
S
Saran

使用 Deque 而不是 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]

很好,谢谢你的例子。
i
irudyak

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

如果您查看通用实现here - 您会发现集合、映射和列表的实现方法一致。

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

我们可以使用首选的声明风格,例如 Set set = new HashSet();请参阅此处的原因。

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

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


E
Eric Aya

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


grep "除此之外"
e
eddy

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


grep "除此之外"
s
sfarshian

Deque 在实现方面优于堆栈有几个原因:

双端队列是接口,栈是类:在类创建中,实现接口比扩展类更好,因为扩展后你不能扩展另一个类,你只能实现一个接口,而当你实现一个接口时,你可以扩展一个类并实现另一个接口。同步:因为栈类是向量类的子类,所以栈也是异步的,但Deque不是。因此,如果不需要同步,那么为了获得更好的性能,我们应该使用 Deque。 Deque 的迭代器按照我们期望的堆栈方式工作:堆栈中的迭代是自下而上的(FIFO(先进先出))。但是 Deque 中的迭代是从上到下的(LIFO(后进先出))。 Stack 不是真正的 LIFO:我们知道 stack 是 vector 类的子类,因此我们可以通过元素的索引访问元素,这违反了 LIFO 协定。