Warum sollte ich Deque over Stack verwenden?

157

Ich benötige eine StackDatenstruktur für meinen Anwendungsfall. Ich sollte in der Lage sein, Elemente in die Datenstruktur zu verschieben, und ich möchte nur das letzte Element aus dem Stapel abrufen. Das JavaDoc für Stack sagt:

Ein vollständigerer und konsistenterer Satz von LIFO-Stapeloperationen wird von der Deque-Schnittstelle und ihren Implementierungen bereitgestellt, die dieser Klasse vorgezogen werden sollten. Beispielsweise:

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

Ich möchte hier definitiv kein synchronisiertes Verhalten, da ich diese Datenstruktur lokal für eine Methode verwenden werde. Abgesehen davon , warum sollte ich es vorziehen , Dequeüber Stackhier?

PS: Der Javadoc von Deque sagt:

Deques können auch als LIFO-Stapel (Last-In-First-Out) verwendet werden. Diese Schnittstelle sollte der älteren Stack-Klasse vorgezogen werden.

Geek
quelle
1
Es bietet mehr eingebaute Methoden, oder besser gesagt "eine vollständigere und konsistentere Menge" von ihnen, die die Menge an Code reduzieren, die Sie schreiben müssen, wenn Sie sie nutzen?

Antworten:

189

Zum einen ist es in Bezug auf die Vererbung sinnvoller. Die Tatsache, die Stacksich Vectorausdehnt, ist meiner Ansicht nach wirklich seltsam. Zu Beginn von Java wurde die Vererbung IMO überbeansprucht - Propertiesein weiteres Beispiel.

Für mich ist das entscheidende Wort in den von Ihnen zitierten Dokumenten konsistent . Dequemacht eine Reihe von Operationen verfügbar, bei denen es darum geht, Elemente vom Anfang oder Ende einer Sammlung abzurufen, hinzuzufügen oder zu entfernen, zu iterieren usw. - und das war's. Es gibt absichtlich keine Möglichkeit, auf ein Element nach Position zuzugreifen, wodurch es Stackverfügbar gemacht wird, da es sich um eine Unterklasse von handelt Vector.

Oh, und Stackhat auch keine Schnittstelle. Wenn Sie also wissen, dass Sie StackOperationen benötigen, müssen Sie sich auf eine bestimmte konkrete Klasse festlegen, was normalerweise keine gute Idee ist.

Auch wie in den Kommentaren erwähnt, Stackund Dequehaben umgekehrte Iterationsreihenfolgen:

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

Dies wird auch in den JavaDocs für Deque.iterator () erläutert :

Gibt einen Iterator über die Elemente in dieser Deque in der richtigen Reihenfolge zurück. Die Elemente werden in der Reihenfolge vom ersten (Kopf) bis zum letzten (Schwanz) zurückgegeben.

Jon Skeet
quelle
10
Im Javadoc von ArrayDeque heißt es: "Diese Klasse ist wahrscheinlich schneller als Stack, wenn sie als Stack verwendet wird, und schneller als LinkedList, wenn sie als Warteschlange verwendet wird." ..Wie gebe ich an, ob ich dies als Stapel oder als Warteschlange verwenden möchte?
Geek
23
@Geek: Das tust du nicht. Der Punkt ist , dass , wenn Sie Warteschlangen Verhalten möchten, Sie könnten verwenden LinkedList, aber ArrayDequeuewerden (oft) schneller sein. Wenn Sie ein Stapelverhalten wünschen, können Sie es verwenden Stack, es ArrayDequewird jedoch (häufig) schneller sein.
Jon Skeet
7
Ist es nicht weniger sinnvoll in Bezug auf die Abstraktion? Ich meine, keine der beiden Lösungen ist in Bezug auf die Abstraktion wirklich gut, da Stacksie das Problem der Wiederholungsbelichtung hat, aber wenn ich eine Stack-Datenstruktur möchte, möchte ich Methoden wie Push, Pop und Peek aufrufen können und nicht Dinge, die es müssen mache mit dem anderen Ende des Stapels.
PeteyPabPro
4
@ JonSkeet auch der Iterator von Stack ist falsch, da er von unten nach oben anstatt von oben nach unten iteriert. stackoverflow.com/questions/16992758/…
Pavel
1
@PeteyPabPro: Du hast recht. Die Verwendung von Dequeue als Stack ermöglicht weiterhin die Verwendung von Nicht-LIFO als geerbte Vector-Methoden von Stack. Eine Erklärung des Problems und eine Lösung (mit Kapselung) finden Sie hier: baddotrobot.com/blog/2013/01/10/stack-vs-deque
rics
4

Hier ist meine Interpretation der Inkonsistenz, die in der Beschreibung der Stapelklasse erwähnt wird.

Wenn Sie sich hier Allzweckimplementierungen ansehen, werden Sie feststellen, dass es einen konsistenten Ansatz für die Implementierung von Set, Map und List gibt.

  • Für Set und Map haben wir 2 Standardimplementierungen mit Hash-Maps und Bäumen. Die erste wird am häufigsten verwendet und die zweite wird verwendet, wenn wir eine geordnete Struktur benötigen (und sie implementiert auch eine eigene Schnittstelle - SortedSet oder SortedMap).

  • Wir können den bevorzugten Deklarationsstil verwenden, wie hierSet<String> set = new HashSet<String>(); Gründe zu sehen .

Aber Stack-Klasse: 1) haben keine eigene Schnittstelle; 2) ist eine Unterklasse der Vektorklasse - die auf einem anpassbaren Array basiert; Wo ist also die Implementierung der verknüpften Liste des Stapels?

In der Deque-Schnittstelle gibt es keine derartigen Probleme, einschließlich zweier Implementierungen (anpassbares Array - ArrayDeque; verknüpfte Liste - LinkedList).

irudyak
quelle
2

Ein weiterer Grund für die Verwendung von Dequeue over Stack ist, dass Dequeue Streams verwenden kann, die in Listen konvertiert werden, wobei das LIFO-Konzept angewendet wird, während Stack dies nicht tut.

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]
Amer Qarabsa
quelle
2

Hier sind einige Gründe, warum Deque besser ist als Stack:

Objektorientiertes Design - Vererbung, Abstraktion, Klassen und Schnittstellen: Stack ist eine Klasse, Deque ist eine Schnittstelle. Es kann nur eine Klasse erweitert werden, während eine beliebige Anzahl von Schnittstellen von einer einzelnen Klasse in Java implementiert werden kann (Mehrfachvererbung des Typs). Durch die Verwendung der Deque-Schnittstelle wird die Abhängigkeit von der konkreten Stack-Klasse und ihren Vorfahren aufgehoben und Sie erhalten mehr Flexibilität, z. B. die Freiheit, eine andere Klasse zu erweitern oder verschiedene Implementierungen von Deque (wie LinkedList, ArrayDeque) auszutauschen.

Inkonsistenz: Stack erweitert die Vector-Klasse, mit der Sie über den Index auf Elemente zugreifen können. Dies steht im Widerspruch zu dem, was ein Stack tatsächlich tun sollte, weshalb die Deque-Schnittstelle bevorzugt wird (solche Operationen sind nicht zulässig). Die zulässigen Operationen stimmen mit den Anforderungen einer FIFO- oder LIFO-Datenstruktur überein.

Leistung: Die von Stack erweiterte Vector-Klasse ist im Grunde die "thread-sichere" Version einer ArrayList. Die Synchronisierungen können möglicherweise zu erheblichen Leistungseinbußen für Ihre Anwendung führen. Wenn Sie andere Klassen mit nicht benötigten Funktionen erweitern (wie in Nr. 2 erwähnt), werden Ihre Objekte aufgebläht, was möglicherweise viel zusätzlichen Speicher und Leistungsaufwand kostet.

Arpit Bhargava
quelle
-1

Für mich fehlte dieser spezielle Punkt: Stack ist Threadsafe, da es von Vector abgeleitet ist, während die meisten Deque-Implementierungen dies nicht tun, und daher schneller, wenn Sie es nur in einem einzelnen Thread verwenden.

Und ich
quelle
grep "Abgesehen davon"
Pacerier
-1

Leistung könnte ein Grund sein. Ein von mir verwendeter Algorithmus ging von 7,6 Minuten auf 1,5 Minuten zurück, indem nur Stack durch Deque ersetzt wurde.

Wirbel
quelle
grep "Abgesehen davon"
Pacerier
-6

Deque wird verwendet, wenn Sie Elemente sowohl vom Kopf als auch vom Schwanz abrufen möchten. Wenn Sie einen einfachen Stapel wünschen, müssen Sie sich nicht für eine Deque entscheiden.

Kante
quelle
1
Bitte beachten Sie den letzten Absatz in der Frage. Der Javadoc sagt, Deques sollte verwendet werden und ich wollte wissen warum?
Geek
2
Deque wird bevorzugt, da Sie nicht auf Elemente in der Mitte zugreifen können. Es ist doppelendig, kann also FILO für FIFO sein.
Thufir
Ich denke, was sie sagen wollen, ist, dass die Klasse die Stapeloperationen als explizite Methoden unterstützt. Siehe Dokumentation und Methoden. Es hat explizite Unterstützung für die Implementierung eines Stacks.
Abhishek Nandgaonkar