Zum Inhalt springen

Ringpuffer (Ring Buffer): Funktionsweise und Einsatz in Java

Ein dicker Ring aus Mattglas vor Pflaume, genau von oben: Sechs runde Mulden im gleichen Abstand; in den drei Mulden oben und rechts liegt je eine leuchtende Bernsteinkugel, die drei Mulden links und unten sind leer

Ein Ringpuffer ist ein Array fester Größe, das du wie einen Kreis benutzt: Ist das letzte Feld beschrieben, geht es mit dem ersten weiter. Zwei Indizes – einer zum Schreiben, einer zum Lesen – wandern dabei immer in dieselbe Richtung, und kein Element wird jemals verschoben.

In diesem Artikel erfährst du:

  • wie ein Ringpuffer Schritt für Schritt funktioniert – und warum sich an den beiden Indizes allein nicht ablesen lässt, ob er voll oder leer ist,
  • wie du einen Ringpuffer in Java selbst implementierst,
  • welche drei Varianten es gibt und wo sie im JDK stecken: in ArrayBlockingQueue, ArrayDeque und MemoryHandler,
  • und was der LMAX Disruptor anders macht.

Was ist ein Ringpuffer?

Ein Ringpuffer (englisch „ring buffer“ oder „circular buffer“) ist ein Array fester Länge, dessen Felder du dir auf einem Kreis angeordnet vorstellen kannst: Auf das letzte Feld folgt wieder das erste. Zwei Indizes zeigen in das Array – der Schreibindex auf das nächste freie Feld, der Leseindex auf das älteste Element.

Die folgende Grafik zeigt denselben Ringpuffer zweimal: links als Kreis, rechts als flaches Array. Er hat sechs Felder; in den Feldern 2 bis 4 liegen die Elemente 3, 4 und 5. readIndex zeigt auf die 3 in Feld 2, writeIndex auf das freie Feld 5. Die beiden Pfeile geben die Schreibrichtung an – im Kreis im Uhrzeigersinn, im Array nach rechts.

Links ein Ring aus sechs Feldern, im Uhrzeigersinn mit 0 bis 5 beschriftet, die Felder 2, 3 und 4 enthalten 3, 4 und 5; ein Pfeil readIndex zeigt auf Feld 2, ein Pfeil writeIndex auf Feld 5; in der Mitte des Rings ein im Uhrzeigersinn gebogener Pfeil. Rechts dasselbe Array flach: sechs Felder nebeneinander, 3, 4 und 5 in den Feldern 2 bis 4, darunter readIndex unter Feld 2 und writeIndex unter Feld 5, darüber ein Pfeil nach rechts
Kreis und Array zeigen denselben Puffer – geschrieben wird im Uhrzeigersinn bzw. nach rechts, und auf Feld 5 folgt Feld 0

Elemente werden am Schreibindex eingefügt und am Leseindex entnommen, in der Reihenfolge, in der sie geschrieben wurden. Ein Ringpuffer ist damit eine Queue nach dem FIFO-Prinzip, deren Kapazität von Anfang an feststeht.

Für diese Datenstruktur gibt es mehrere Namen, die alle dasselbe meinen: „ring buffer“, „circular buffer“, „Ringbuffer“, „Ringspeicher“ – und „circular array“ für das Array selbst. Eine „circular queue“ ist die Queue, die auf einem solchen Array aufsetzt.

Was gewinnst du durch den Kreis? Beim Entnehmen muss kein Element nachrücken, und beim Einfügen muss kein Speicher angefordert werden: Das Array wird einmal angelegt und danach nur noch beschrieben. Schreiben und Lesen kosten deshalb immer dieselbe Zeit, unabhängig davon, wie voll der Puffer ist – mehr dazu im Abschnitt „Zeitkomplexität der Ringpuffer-Operationen“.

So funktioniert ein Ringpuffer

Schreiben und Lesen mit zwei Indizes

Beim Schreiben landet das neue Element in dem Feld, auf das writeIndex zeigt; danach rückt writeIndex um ein Feld weiter. Beim Lesen wird das Element aus dem Feld entnommen, auf das readIndex zeigt – das Feld wird auf null gesetzt –, und readIndex rückt um ein Feld weiter.

Beide Indizes bewegen sich also nur in eine Richtung. Der Leseindex läuft dem Schreibindex hinterher, und zwischen den beiden liegen die Elemente, die noch nicht gelesen wurden.

In der Grafik oben liegen drei Elemente dazwischen: die 3, die 4 und die 5. Die 1 und die 2 wurden bereits gelesen – ihre Felder 0 und 1 sind wieder frei.

Die folgende Grafik zeigt beide Operationen am Kreis, von links nach rechts. Links der Puffer aus der ersten Grafik. In der Mitte derselbe Puffer, nachdem write(6) die 6 in Feld 5 abgelegt hat – writeIndex steht jetzt auf dem Nachbarfeld 0. Rechts der Puffer, nachdem read() die 3 aus Feld 2 entnommen hat – readIndex steht jetzt auf Feld 3:

Drei Ringe aus je sechs Feldern nebeneinander. Links, vor write(6): 3, 4 und 5 in den Feldern 2 bis 4, writeIndex zeigt auf Feld 5, readIndex auf Feld 2. In der Mitte, nach write(6): zusätzlich die 6 in Feld 5, hervorgehoben; writeIndex zeigt auf Feld 0, readIndex weiterhin auf Feld 2. Rechts, nach read(): Feld 2 ist leer, 4, 5 und 6 liegen in den Feldern 3 bis 5; readIndex zeigt auf Feld 3, writeIndex auf Feld 0
Schreiben rückt writeIndex vor, Lesen rückt readIndex nach – beide im Uhrzeigersinn

Im Kreis ist an write(6) nichts Besonderes: Feld 0 ist der Nachbar von Feld 5. Im flachen Array wird derselbe Schritt zum Sprung – darum geht es im nächsten Abschnitt.

Der Umbruch: vom letzten Feld zurück auf 0

Was passiert im Array, wenn writeIndex auf dem letzten Feld steht? Das zeigt die folgende Grafik. Oben der Puffer aus der ersten Grafik, writeIndex steht auf Feld 5. Unten derselbe Puffer, nachdem die 6 geschrieben wurde: Sie liegt in Feld 5, und writeIndex ist auf Feld 0 gesprungen:

Zwei Reihen mit je sechs Feldern. Oben, vor write(6): 3, 4 und 5 in den Feldern 2 bis 4, readIndex unter Feld 2, writeIndex unter Feld 5. Unten, nach write(6): zusätzlich die 6 in Feld 5, hervorgehoben; readIndex weiterhin unter Feld 2, writeIndex jetzt unter Feld 0
Nach Feld 5 kommt Feld 0 – ein Index wird nie so groß wie die Kapazität

Dieser Sprung ist der ganze Trick des Ringpuffers. Im Code gibt es drei Wege, ihn zu schreiben:

  1. Vergleich: Der Index wird um 1 erhöht; ist er danach gleich der Länge des Arrays, wird er auf 0 gesetzt. So macht es das JDK, z. B. in ArrayBlockingQueue: if (++putIndex == items.length) putIndex = 0;
  2. Modulo: Der neue Index ist (index + 1) % capacity. Für jeden Index unterhalb von capacity - 1 ist das index + 1, für capacity - 1 selbst ist es 0. So rechnet der MemoryHandler aus java.util.logging: Sein nächstes Schreibfeld ist (start + count) % buffer.length.
  3. Bitmaske: Ist die Kapazität eine Zweierpotenz, etwa 8, liefert (index + 1) & (capacity - 1) dasselbe Ergebnis wie Modulo: Die Maske capacity - 1 besteht binär nur aus Einsen (für 8: 0b111), und ein bitweises Und mit ihr schneidet alle höheren Bits ab – aus 8 (0b1000) wird 0. So macht es der LMAX Disruptor, der deshalb nur Zweierpotenzen als Puffergröße akzeptiert.

Alle drei Wege führen zum selben Ergebnis; sie unterscheiden sich darin, was die CPU ausführt: einen Vergleich, eine Division oder ein bitweises Und. Ich empfehle den Vergleich: Er funktioniert mit jeder Kapazität, und das JDK macht es in ArrayDeque und ArrayBlockingQueue genauso. Die Bitmaske lohnt sich erst, wenn die Kapazität ohnehin eine Zweierpotenz ist.

Voll oder leer? Drei Antworten

Ist der Puffer voll, zeigt writeIndex auf das Feld, in dem das älteste Element liegt – also auf dasselbe Feld wie readIndex. Ist der Puffer leer, hat readIndex den writeIndex eingeholt: Beide zeigen wieder auf dasselbe Feld.

Die folgende Grafik zeigt beide Fälle. Links ein voller Puffer mit den Elementen 3 bis 8 – nach der 6 in Feld 5 wurden die 7 und die 8 in die Felder 0 und 1 geschrieben –, rechts ein leerer Puffer. In beiden Puffern stehen readIndex und writeIndex auf Feld 2; an den Indizes allein ist der Unterschied nicht zu erkennen:

Zwei Ringe aus je sechs Feldern. Links enthalten die Felder 2 bis 5 die Werte 3 bis 6 und die Felder 0 und 1 die Werte 7 und 8, darunter steht „voll: size = 6“. Rechts sind alle sechs Felder leer, darunter steht „leer: size = 0“. In beiden Ringen zeigt ein Pfeil mit den Beschriftungen readIndex und writeIndex auf Feld 2
Gleiche Indizes, anderer Zustand – erst der Zähler unterscheidet voll von leer

Drei Lösungen haben sich etabliert, und alle drei findest du im JDK:

  1. Ein Zähler. Der Puffer führt neben den beiden Indizes die Anzahl der Elemente mit: size == 0 heißt leer, size == capacity heißt voll. So macht es ArrayBlockingQueue mit ihrem Feld count – und so mache ich es in der Implementierung im Abschnitt „Ringpuffer in Java implementieren“.
  2. Ein Feld bleibt frei. Der Puffer wird nie ganz gefüllt; writeIndex zeigt immer auf ein leeres Feld. Dann heißt readIndex == writeIndex eindeutig „leer“, und voll ist der Puffer, wenn das Feld nach writeIndex das Feld readIndex ist. Der Preis ist ein zusätzliches Feld im Array – ArrayDeque zahlt diesen Preis und legt für 16 Elemente ein Array mit 17 Feldern an.
  3. Das Feld prüfen. Zeigen beide Indizes auf dasselbe Feld, entscheidet dessen Inhalt: Ist er null, ist der Puffer leer, ansonsten voll. Das funktioniert nur, wenn null kein gültiges Element ist – ArrayDeque und ArrayBlockingQueue lehnen null beide ab. ArrayDeque braucht diese Prüfung, obwohl es ein Feld frei hält: addLast() schreibt das neue Element erst in das letzte freie Feld und vergrößert das Array danach. In diesem Moment ist das Array voll, und head == tail sähe aus wie „leer“ – deshalb prüft grow() das Feld elements[head], bevor es den Inhalt auseinanderschiebt.

Ich empfehle den Zähler. Er hält den Code am besten lesbar und macht size() zu einem Feldzugriff – für den Preis eines zusätzlichen int-Felds.

Ringpuffer in Java implementieren

Ein Ringpuffer braucht lediglich ein Array, zwei Indizes und einen Zähler. Die folgende Klasse RingBuffer zeigt diese Minimalform – ohne Threadsicherheit: Greifen mehrere Threads gleichzeitig zu, brauchst du ein Lock wie in der ArrayBlockingQueue.

Den Code samt Unit-Tests findest du auch in der Klasse RingBuffer im GitHub-Repository zu diesem Artikel:

public class RingBuffer<E> {

  private final Object[] elements;
  private int readIndex;
  private int writeIndex;
  private int size;

  public RingBuffer(int capacity) {
    if (capacity < 1) {
      throw new IllegalArgumentException("Capacity must be 1 or higher");
    }
    elements = new Object[capacity];
  }

  public void write(E element) {
    if (isFull()) {
      throw new IllegalStateException("The ring buffer is full");
    }
    elements[writeIndex] = element;
    writeIndex = next(writeIndex);
    size++;
  }

  public E read() {
    if (isEmpty()) {
      throw new NoSuchElementException("The ring buffer is empty");
    }

    @SuppressWarnings("unchecked")
    E element = (E) elements[readIndex];

    elements[readIndex] = null;
    readIndex = next(readIndex);
    size--;
    return element;
  }

  public boolean isEmpty() {
    return size == 0;
  }

  public boolean isFull() {
    return size == elements.length;
  }

  public int size() {
    return size;
  }

  private int next(int index) {
    index++;
    if (index == elements.length) {
      index = 0;
    }
    return index;
  }
}

Die Methoden im Einzelnen:

  • write() prüft zuerst, ob der Puffer voll ist, legt das Element in dem Feld ab, auf das writeIndex zeigt, rückt den Index weiter und erhöht den Zähler.
  • read() prüft, ob der Puffer leer ist, holt das Element aus dem Feld, auf das readIndex zeigt, setzt das Feld auf null – damit der Garbage Collector das Element einsammeln kann, sobald sonst niemand mehr eine Referenz darauf hat –, rückt den Index weiter und verringert den Zähler.
  • next() ist die eine Stelle, an der die Indizes umbrechen: der Vergleich aus dem Abschnitt „Der Umbruch: vom letzten Feld zurück auf 0“. Willst du Modulo oder die Bitmaske ausprobieren, änderst du nur diese Methode.
  • isEmpty(), isFull() und size() lesen den Zähler.

Das Array hat den Typ Object[], weil new E[capacity] in Java nicht erlaubt ist; der Cast in read() ist trotzdem sicher, denn write() lässt nur Elemente vom Typ E hinein.

Das folgende Beispiel schreibt drei Elemente in einen Puffer mit der Kapazität 3, liest zwei davon und schreibt zwei weitere – die landen in den Feldern 0 und 1, während „c“ noch in Feld 2 liegt (→ Code auf GitHub):

RingBuffer<String> buffer = new RingBuffer<>(3);

buffer.write("a");
buffer.write("b");
buffer.write("c");
System.out.println("read: " + buffer.read());
System.out.println("read: " + buffer.read());

buffer.write("d"); // landet in Feld 0
buffer.write("e"); // landet in Feld 1
System.out.println("full: " + buffer.isFull());

while (!buffer.isEmpty()) {
  System.out.println("read: " + buffer.read());
}

Das Programm gibt Folgendes aus:

read: a
read: b
full: true
read: c
read: d
read: e

Die Elemente kommen in der Reihenfolge heraus, in der sie geschrieben wurden – der Umbruch von Feld 2 auf Feld 0 ist von außen nicht zu sehen.

Eine Fassung derselben Idee mit enqueue() und dequeue() hinter einem Queue-Interface ist die BoundedArrayQueue aus dem Artikel „Queue mit einem Array implementieren“ – dort zeige ich auch, wie das Array wächst, wenn die Queue nicht bounded bleiben soll.

Varianten des Ringpuffers

Was passiert, wenn der Puffer voll ist und ein weiteres Element geschrieben werden soll? Darauf gibt es drei Antworten, und jede hat ihren Vertreter im JDK.

Fester Puffer: ArrayBlockingQueue lehnt ab oder wartet

Die erste Antwort: Der Puffer nimmt das Element nicht an. ArrayBlockingQueue erkennt die volle Queue an ihrem Zähler – count == items.length – und reagiert je nach Methode anders: offer() liefert false, add() wirft eine IllegalStateException mit der Meldung „Queue full“, und put() lässt den aufrufenden Thread warten, bis ein Feld frei wird. offer() mit Timeout wartet höchstens die angegebene Zeit und liefert false, wenn bis dahin kein Feld frei geworden ist.

Im Quelltext (JDK 27) ist das Warten eine Schleife: Solange count == items.length gilt, ruft put() die Methode notFull.await() auf. Jede Entnahme – ob über take() oder poll() – signalisiert die Condition notFull und weckt damit einen wartenden Producer. Die Condition hängt an dem einen ReentrantLock der Klasse; warum sich Producer und Consumer damit gegenseitig ausbremsen können, zeige ich im Abschnitt „Ist ArrayBlockingQueue threadsicher?“ des Artikels über die ArrayBlockingQueue.

Wachsender Puffer: ArrayDeque legt ein größeres Array an

Die zweite Antwort: Der Puffer wächst. ArrayDeque hat keine Kapazitätsgrenze – ist das Array voll, legt die Klasse ein größeres an und kopiert die Elemente um.

Voll ist das Array, sobald nach dem Einfügen head und tail auf dasselbe Feld zeigen. Dann ruft die Einfügemethode – z. B. addLast() – sofort grow() auf. Ein Array mit weniger als 64 Feldern wächst um seine Länge plus 2 – aus den 17 Feldern des Standardkonstruktors werden 36 –, ein größeres um die Hälfte.

Läuft der Inhalt gerade über das Arrayende hinaus und setzt sich bei Feld 0 fort, verschiebt grow() den Teil von head bis zum alten Arrayende an das Ende des neuen Arrays, damit die Reihenfolge erhalten bleibt. Wie ein wachsendes Array beim Deque umkopiert wird, zeige ich mit Grafiken im Artikel „Deque mit einem Array implementieren“; die Eigenschaften der Klasse stehen im Artikel über das ArrayDeque.

Überschreibender Puffer: Die ältesten Werte fallen raus

Die dritte Antwort: Das neue Element überschreibt das älteste. Der Puffer ist dann nie voll im Sinne von „nimmt nichts mehr an“ – er enthält immer die jüngsten Elemente, höchstens so viele, wie er Felder hat. Das ist die passende Variante für alles, wovon dich nur die letzten n Werte interessieren: die letzten 1.000 Logzeilen, die Messwerte der letzten Minute, die letzten Positionen einer Spielfigur.

Die folgende Grafik zeigt den vollen Puffer aus der vorherigen Grafik vor und nach write(9): Die 9 ersetzt in Feld 2 die 3 – das älteste Element –, und beide Indizes rücken auf Feld 3 weiter. Die 4 ist jetzt das älteste Element:

Zwei Reihen mit je sechs Feldern. Oben, vor write(9): 7, 8, 3, 4, 5, 6 in den Feldern 0 bis 5, readIndex und writeIndex beide unter Feld 2. Unten, nach write(9): in Feld 2 steht die 9, hervorgehoben, die übrigen Felder unverändert; readIndex und writeIndex beide unter Feld 3
Überschreiben heißt: Leseindex und Schreibindex rücken gemeinsam weiter

Im Code ist das eine if-Abfrage mehr. Die Klasse OverwritingRingBuffer erbt von RingBuffer und überschreibt nur write() (→ Code auf GitHub):

public class OverwritingRingBuffer<E> extends RingBuffer<E> {

  public OverwritingRingBuffer(int capacity) {
    super(capacity);
  }

  @Override
  public void write(E element) {
    if (isFull()) {
      read(); // verwirft das älteste Element
    }
    super.write(element);
  }
}

Ist der Puffer voll, liest write() das älteste Element und verwirft es – damit rückt readIndex weiter, und ein Feld wird frei. Dann schreibt die geerbte Methode das neue Element wie gewohnt.

Schreibst du fünf Zahlen in einen Puffer mit der Kapazität 3, bleiben die letzten drei übrig (→ Code auf GitHub):

RingBuffer<Integer> buffer = new OverwritingRingBuffer<>(3);

for (int i = 1; i <= 5; i++) {
  buffer.write(i);
}
System.out.println("size: " + buffer.size());

while (!buffer.isEmpty()) {
  System.out.println("read: " + buffer.read());
}

Die Ausgabe:

size: 3
read: 3
read: 4
read: 5

Einen überschreibenden Ringpuffer hat auch das JDK – nicht in java.util, sondern in java.util.logging. Der MemoryHandler hält Log-Einträge in einem Array von LogRecords, laut Javadoc einem „circular buffer“. Ist der Puffer voll, überschreibt der neue Eintrag den ältesten, und start – der Index des ältesten Eintrags – rückt um ein Feld weiter.

Damit hält der MemoryHandler immer die jüngsten Einträge bereit. An einen anderen Handler gibt er sie erst weiter, wenn ein Eintrag mit mindestens dem Level pushLevel eintrifft – standardmäßig SEVERE. Bei einem Fehler landen deshalb auch die bis zu 1.000 Einträge davor im Log (die Standardgröße des Puffers), ohne dass du dauerhaft alles mitschreiben musst.

LMAX Disruptor: der bekannteste Java-Ringpuffer

Außerhalb des JDK ist der bekannteste Ringpuffer in Java der LMAX Disruptor – eine Bibliothek der Handelsplattform LMAX Exchange, um Events zwischen Threads auszutauschen (Maven-Koordinaten com.lmax:disruptor, im Oktober 2026 in Version 4.0.0). Ihr Kern ist die Klasse RingBuffer, laut ihrem Javadoc ein „ring based store of reusable entries“.

Drei Dinge macht der Disruptor anders als die Puffer in diesem Artikel:

  • Die Einträge sind vorab angelegt. Beim Erzeugen füllt der RingBuffer jedes Feld über eine EventFactory mit einem leeren Event-Objekt. Ein Producer legt kein neues Objekt an, sondern kopiert seine Daten in die Felder des vorhandenen Events – im Beispiel der Disruptor-Doku einen long, den er aus einem ByteBuffer liest: event.set(buffer.getLong(0)). Weil die Events so lange leben wie der Disruptor, entsteht dabei kein Müll für den Garbage Collector. Das gilt nur, solange die Daten als Werte kopiert werden: Speichert der Producer im Event bloß die Referenz auf ein eigens angelegtes Objekt, entsteht derselbe Müll wie bei einer ArrayBlockingQueue.
  • Statt zweier Indizes gibt es Sequenznummern. Ein Producer fordert mit next() die nächste Sequenznummer an – ein long, das immer weiter zählt –, befüllt den Eintrag und gibt ihn mit publish() frei. Jeder Consumer führt seine eigene Sequence, bis zu der er gelesen hat; so weiß der Disruptor, welche Einträge noch nicht alle Consumer gelesen haben und deshalb nicht überschrieben werden dürfen.
  • Die Kapazität muss eine Zweierpotenz sein. Der Konstruktor prüft das mit Integer.bitCount(bufferSize) != 1 und wirft sonst eine IllegalArgumentException („bufferSize must be a power of 2“). Das Feld zu einer Sequenznummer ist dann sequence & indexMask mit indexMask = bufferSize - 1 – die Bitmaske aus dem Abschnitt „Der Umbruch: vom letzten Feld zurück auf 0“.

Willst du nur Elemente von einem Thread an einen anderen übergeben, ist eine ArrayBlockingQueue die einfachere Wahl. Der Disruptor lohnt sich, wenn die Latenz pro Event und jede Objektallokation im Betrieb zählen – dafür wurde er gebaut.

Zeitkomplexität der Ringpuffer-Operationen

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

Schreiben und Lesen greifen auf je ein Feld zu und rücken einen Index weiter – egal, wie viele Elemente im Puffer liegen. Ihre Zeitkomplexität ist O(1). Dasselbe gilt für isEmpty(), isFull() und size(), wenn der Puffer einen Zähler führt.

Wächst der Puffer wie bei ArrayDeque, kostet das Umkopieren beim Vergrößern einmalig O(n). Weil das Array dabei jedes Mal proportional zu seiner Größe wächst, kommt das selten vor, und über viele Einfügeoperationen gemittelt bleibt es bei amortisiert O(1).

Ein Element in der Mitte zu suchen oder zu entfernen kostet dagegen O(n): Dafür muss das Array durchlaufen und beim Entfernen alles hinter der Lücke aufgerückt werden – ArrayBlockingQueue.remove(Object) tut genau das.

Zusammenfassung und Ausblick

Ein Ringpuffer ist ein Array mit zwei Indizes, die, wenn sie das Ende des Arrays erreicht haben, wieder auf 0 springen. Schreiben und Lesen verschieben nie ein Element, und solange der Puffer nicht wachsen muss, legen sie keinen Speicher an – deshalb liegt ein Ringpuffer unter ArrayBlockingQueue, ArrayDeque und dem MemoryHandler des Logging-Pakets, und deshalb hat der LMAX Disruptor ihn zum Kern seines Designs gemacht.

Drei Punkte zum Mitnehmen:

  • Ob der Puffer voll oder leer ist, sagen dir die Indizes allein nicht. Du brauchst einen Zähler, ein frei bleibendes Feld oder einen Blick in das Feld, auf das beide zeigen.
  • Ist der Puffer voll, hast du drei Möglichkeiten: ablehnen oder warten (ArrayBlockingQueue), wachsen (ArrayDeque) oder das älteste Element überschreiben (MemoryHandler, OverwritingRingBuffer).
  • Den Umbruch vom letzten Feld zurück auf 0 schreibst du am besten als Vergleich; die Bitmaske lohnt sich erst bei einer Kapazität, die ohnehin eine Zweierpotenz ist.

Wie du aus dem Ringpuffer eine Queue mit wachsendem Array machst, zeige ich im Artikel „Queue mit einem Array implementieren“ – und wie du an beiden Enden einfügst und entnimmst, im Artikel „Deque mit einem Array implementieren“.

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