Zum Inhalt springen

Queue-Datenstruktur: Was ist eine Queue (Warteschlange)?

Sieben Filzkugeln in einer Reihe in einer Acrylrinne auf Messingstützen vor Apricot; links fällt eine neue Kugel hinein, rechts rollt die vorderste hinaus

In diesem Tutorial lernst du den abstrakten Datentyp „Queue“ kennen (im Deutschen auch als „Warteschlange“ bezeichnet):

  • Wie funktioniert eine Queue?
  • Wie heißen ihre Operationen – und wie heißen sie in Java?
  • Welche Varianten der Queue gibt es?
  • Was sind die Anwendungsgebiete für Queues?
  • Wie aufwendig sind die Enqueue- und Dequeue-Operationen?

Was ist eine Queue?

Eine Queue ist eine Folge von Elementen, bei der die Elemente auf einer Seite eingefügt und in derselben Reihenfolge auf der anderen Seite wieder entnommen werden.

Man kann sich das wie eine Warteschlange an einer Kasse oder bei einer Behörde vorstellen:

Warteschlange an einer Kasse: Hinter der Kasse steht eine Person, davor warten sechs Personen mit Einkaufskörben hintereinander, alle mit Blick zur Kasse
Warteschlange an der Kasse: Wer vorn steht, wird bedient

Ankommende Personen stellen sich am Ende der Schlange (rechts im Bild) an. Sobald eine Person abgefertigt wurde, kommt die nächste vom Kopf der Schlange (links) an die Reihe.

Die Person, die sich zuerst angestellt hat, kommt somit auch zuerst an die Reihe. Daher sprechen wir vom First-in-First-out-Prinzip (FIFO).

FIFO-Prinzip bei Queues

Beim abstrakten Datentyp „Queue“ kann das wie im folgenden Beispiel aussehen:

Queue-Datenstruktur: Enqueue fügt das Element 14 am Ende der Queue ein, Dequeue entnimmt das Element 5 am Kopf
Queue: Elemente werden am Ende eingefügt und am Kopf entnommen

Die Grafik zeigt eine Queue, die die Elemente 6, 7, 8 usw. bis 13 enthält. Die 5 wurde gerade vom Kopf der Queue (englisch „Front“ oder „Head“, links im Bild) entnommen, und die 14 wird gerade an das Ende der Queue (englisch „Back“, „Tail“ oder „Rear“, rechts im Bild) eingefügt.

Queue-Operationen: Enqueue und Dequeue

Die Operationen der Queue bezeichnen wir wie folgt:

  • „Enqueue“: Einfügen neuer Elemente am Ende der Queue
  • „Dequeue“: Entnehmen von Elementen vom Kopf der Queue
  • „Peek“ oder „Front“: Betrachten des Elements am Kopf, ohne es zu entnehmen (optional)

Aus einer leeren Queue kann die Dequeue-Operation nichts entnehmen. Hat eine Queue eine feste Kapazität und ist sie voll, kann die Enqueue-Operation nichts einfügen. Wie eine Implementierung darauf reagiert – mit einem speziellen Rückgabewert oder mit einer Exception –, legt sie selbst fest.

Die Queue-Operationen in Java

Das Queue-Interface des JDK bietet jede der drei Operationen in zwei Varianten an: Die eine wirft eine Exception, wenn die Operation nicht möglich ist, die andere liefert einen speziellen Rückgabewert.

Operationim Fehlerfall: Exceptionim Fehlerfall: Rückgabewert
Enqueueadd()offer() – false, wenn die Queue voll ist
Dequeueremove()poll() – null, wenn die Queue leer ist
Peekelement()peek() – null, wenn die Queue leer ist

add() wirft eine IllegalStateException, remove() und element() werfen eine NoSuchElementException. Die meisten Queue-Implementierungen haben übrigens keine Kapazitätsgrenze – bei ihnen schlägt das Einfügen nie fehl.

Varianten der Queue

In dieser Tutorial-Serie lernst du drei Abwandlungen der einfachen FIFO-Queue kennen:

  • Priority Queue: Sie entnimmt die Elemente nicht in der Reihenfolge des Einfügens, sondern nach ihrer Priorität. Damit weicht sie vom FIFO-Prinzip ab. Das JDK bietet dafür u. a. die Klasse PriorityQueue an.
  • Deque („Double-ended Queue“): Sie erlaubt das Einfügen und Entnehmen an beiden Enden. Mehr dazu erfährst du in der Tutorial-Serie über Deques.
  • Bounded und Blocking Queue: Eine bounded Queue hat eine feste Kapazität. Eine blocking Queue lässt den aufrufenden Thread warten, bis ein Element verfügbar ist (beim Entnehmen) bzw. bis Platz frei ist (beim Einfügen). Beides erkläre ich im Artikel über das BlockingQueue-Interface.

Anwendungsgebiete für Queues

Ein Anwendungsbereich von Queues, den wir alle kennen, ist die Druckerwarteschlange. Verschiedene Programme stellen dort Druckaufträge ein. In der Regel gibt es nur einen Drucker, der diese dann der Reihe nach abarbeitet.

Ein technisches Anwendungsbeispiel ist die Verarbeitung von HTTP-Requests in einem Webserver. Ein Webserver arbeitet in der Regel mit einem Threadpool für gleichzeitig abzuarbeitende Requests. Wenn mehr Anfragen hereinkommen als gleichzeitig abgearbeitet werden können, ist der Threadpool ausgelastet. Zusätzliche Requests werden dann in eine Warteschlange gestellt und in First-in-First-out-Reihenfolge abgearbeitet, sobald wieder Threads zur Verfügung stehen.

Auch Algorithmen setzen Queues ein. Bei der Breitensuche im Binärbaum merkt sich der Algorithmus die noch zu besuchenden Knoten in einer Queue – so besucht er die Knoten Ebene für Ebene.

Zeitkomplexität der Queue-Operationen

Eine Einführung in das Thema Zeitkomplexität findest du im Artikel „O-Notation und Zeitkomplexität – anschaulich erklärt“.

Queues werden in der Regel mit Arrays oder verketteten Listen implementiert. Bei beiden Varianten ist der Aufwand für die Enqueue- und Dequeue-Operationen jeweils konstant, d. h., er ändert sich nicht mit der Länge der Queue.

Die Zeitkomplexität dieser Operationen lautet also: O(1).

Eine Ausnahme gibt es beim Array: Ist es voll und soll die Queue weiter wachsen, muss die Enqueue-Operation ein neues, größeres Array anlegen und alle Elemente dorthin umkopieren – dieser eine Aufruf kostet O(n). Weil das Array dabei jedes Mal proportional zu seiner Größe wächst (in der ArrayQueue dieser Serie auf das 1,5- bis 2-Fache), kommt das selten vor, und über viele Aufrufe gemittelt bleibt der Aufwand konstant. Man spricht von amortisiert O(1).

Zu Übungszwecken kann man eine Queue auch mit Stacks implementieren (dazu mehr in einem späteren Teil des Tutorials). Mit der dort gezeigten Variante kostet die Enqueue-Operation allerdings O(n): Für jedes neue Element werden alle vorhandenen Elemente zweimal umgeschichtet.

Ausblick

Im nächsten Teil des Tutorials lernst du das Queue-Interface von Java im Detail kennen: seine Methoden, die Klassenhierarchie und welche der JDK-Implementierungen du wann einsetzt.

Konntest du etwas aus diesem Artikel mitnehmen? Mit einer Bewertung auf meinem ProvenExpert-Profil hilfst du anderen Entwickler:innen einzuschätzen, ob sich das Lesen lohnt – und mir zu verstehen, welche Inhalte dir weiterhelfen.

👉 Bewertung abgeben

Datenstrukturen wirklich verstehen?

Dieser Artikel zeigt eine Datenstruktur. „Mastering Data Structures in Java“ zeigt dir alle – und vor allem, wann du welche einsetzt und warum das über die Laufzeit deiner Anwendung entscheidet.

Der Premium-Online-Kurs ist gerade geschlossen. Auf der Warteliste erfährst du als Erste:r, wenn er wieder öffnet – und bekommst das Angebot vor allen anderen.

Zur Warteliste

Werde ein:e bessere:r Java-Entwickler:in

Mit meinem kostenlosen Newsletter bleibst du vorn. Modernes Java: neue Versionen & Features, Performance und JVM-Insights – 1x im Monat.

Suche