Кільцевий буфер на Java


78

У мене є потоковий ряд потокових передач, з яких я зацікавлений у збереженні останніх 4 елементів, а це означає, що я хочу мати можливість вивести перший і додати до кінця. По суті, мені потрібен кільцевий буфер .

Яка колекція Java найкраща для цього? Вектор?


2
LinkedListздається розумним вибором для вставлення та видалення O (1), вам також потрібна індексація O (1)?
Mark Elliot

4
Нитка безпечна? Не захищений від ниток? Як правило, найкращим варіантом є видалення з кінця та додавання на початку LinkedList.
Maurício Linhares

Відповіді:


95

Розглянемо CircularFifoBuffer від Apache Common.Collections . На відміну від Queue, вам не потрібно підтримувати обмежений розмір базової колекції та обгортати її, коли ви досягнете межі.

Buffer buf = new CircularFifoBuffer(4);
buf.add("A");
buf.add("B");
buf.add("C");
buf.add("D"); //ABCD
buf.add("E"); //BCDE

CircularFifoBuffer зробить це за вас через такі властивості:

  • CircularFifoBuffer - це перший вхідний буфер із фіксованим розміром, який замінює його найстаріший елемент, якщо він заповнений.
  • Порядок видалення CircularFifoBuffer базується на порядку вставки; елементи видаляються в тому самому порядку, в якому вони були додані. Порядок ітерацій такий самий, як і порядок видалення.
  • Операції add (Object), BoundedFifoBuffer.remove () та BoundedFifoBuffer.get () виконуються за постійний час . Всі інші операції виконуються в лінійний час або гірше.

Однак слід врахувати і його обмеження - наприклад, ви не можете додавати відсутні колекції часу до цієї колекції, оскільки це не дозволяє нулі.

ПРИМІТКА. При використанні поточних загальних колекцій (4. *) потрібно використовувати чергу. Подобається це:

Queue buf = new CircularFifoQueue(4);

3
здається, зараз існує версія, похідна від оригінальних колекцій Commons, яка використовує загальні засоби: sourceforge.net/projects/collections (схоже, проект було перенесено на github)
Андре Хольцнер,

5
Commons Collections 4.0 включає CircularFifoQueue, який має ті самі властивості та підтримує загальні засоби.
Піт,

Я помітив, що CircularFifoQueue не працює так. Коли я ставлю "ПРИВІТАЙ" у Чергу розміром 3 одиниці, я переходжу від "HEL" до "LEL" до "LOL". Що сталося з функцією, описаною @AndryTaptunov?
ES

Цю особливість запропонував Енді. "heLlo" переходить "heL" -> "leL" -> "loL", перезаписуючи спочатку найстарішу літеру.
Ryan The Leach,

Незалежно від того, як один перебирає CircularFifoQueue (шляхом видалення елементів, використовуючи итератор в черзі в , безпосередньо індексувати по зростанню від 0 поз і т.д.), ви отримаєте очікуваний heL, eLl, Llo. Отже, @RyanTheLeach, здається, ілюструє, як черга зберігає свої елементи, але я не можу відтворити описану поведінку @ES
fspinnenhirn

51

Починаючи з Guava 15.0 (випущений у вересні 2013 року), існує EvictingQueue :

Неблокуюча черга, яка автоматично витісняє елементи з голови черги при спробі додати нові елементи до черги, і вона заповнена. Черга виселення повинна бути налаштована на максимальний розмір. Щоразу, коли елемент додається до повної черги, черга автоматично видаляє свій головний елемент. Це відрізняється від звичайних обмежених черг, які або блокують, або відхиляють нові елементи, коли вони заповнені.

Цей клас не захищений від потоків і не приймає нульові елементи.

Приклад використання:

EvictingQueue<String> queue = EvictingQueue.create(2);
queue.add("a");
queue.add("b");
queue.add("c");
queue.add("d");
System.out.print(queue); //outputs [c, d]

1
@MartinVseticka Так, це O (1).
саміт

14

З Java 1.6 існує ArrayDeque , яка реалізує Queueі, здається, є швидшою та ефективнішою пам’яттю, ніж a, LinkedListі не має накладних витрат на синхронізацію потоків ArrayBlockingQueue: з документації API: "Цей клас, швидше за все, буде швидшим за Stack, коли використовується як стек і швидше, ніж LinkedList, коли використовується як черга. "

final Queue<Object> q = new ArrayDeque<Object>();
q.add(new Object()); //insert element
q.poll(); //remove element

13
Це зростає необмеженим і не поводиться як буфер дзвінка.
scorpiodawg

11

Якщо вам потрібно

  • O (1) вставка та видалення
  • O (1) індексація до елементів інтер’єру
  • доступ лише з одного потоку
  • загальний тип елемента

тоді ви можете використовувати цей CircularArrayList для Java таким чином (наприклад):

CircularArrayList<String> buf = new CircularArrayList<String>(4);

buf.add("A");
buf.add("B");
buf.add("C");
buf.add("D"); // ABCD

String pop = buf.remove(0); // A <- BCD
buf.add("E"); // BCDE

String interiorElement = buf.get(i);

Всі ці методи працюють в O (1).


5

Я мав ту саму проблему деякий час тому і був розчарований, бо не міг знайти рішення, яке відповідає моїм потребам, тому я написав власний клас. Чесно кажучи, я тоді знайшов якийсь код, але навіть це було не те, що я шукав, тому я його адаптував і зараз ділюся ним, як це зробив автор цього фрагмента коду.

EDIT: Це оригінальний (хоча і дещо інший) код: CircularArrayList для Java

У мене немає посилання на джерело, тому що це було давно, але ось код:

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.RandomAccess;

public class CircularArrayList<E> extends AbstractList<E> implements RandomAccess {

private final int n; // buffer length
private final List<E> buf; // a List implementing RandomAccess
private int leader = 0;
private int size = 0;


public CircularArrayList(int capacity) {
    n = capacity + 1;
    buf = new ArrayList<E>(Collections.nCopies(n, (E) null));
}

public int capacity() {
    return n - 1;
}

private int wrapIndex(int i) {
    int m = i % n;
    if (m < 0) { // modulus can be negative
        m += n;
    }
    return m;
}

@Override
public int size() {
    return this.size;
}

@Override
public E get(int i) {
    if (i < 0 || i >= n-1) throw new IndexOutOfBoundsException();

    if(i > size()) throw new NullPointerException("Index is greater than size.");

    return buf.get(wrapIndex(leader + i));
}

@Override
public E set(int i, E e) {
    if (i < 0 || i >= n-1) {
        throw new IndexOutOfBoundsException();
    }
    if(i == size()) // assume leader's position as invalid (should use insert(e))
        throw new IndexOutOfBoundsException("The size of the list is " + size() + " while the index was " + i
                +". Please use insert(e) method to fill the list.");
    return buf.set(wrapIndex(leader - size + i), e);
}

public void insert(E e)
{
    int s = size();     
    buf.set(wrapIndex(leader), e);
    leader = wrapIndex(++leader);
    buf.set(leader, null);
    if(s == n-1)
        return; // we have replaced the eldest element.
    this.size++;

}

@Override
public void clear()
{
    int cnt = wrapIndex(leader-size());
    for(; cnt != leader; cnt = wrapIndex(++cnt))
        this.buf.set(cnt, null);
    this.size = 0;      
}

public E removeOldest() {
    int i = wrapIndex(leader+1);

    for(;;i = wrapIndex(++i)) {
        if(buf.get(i) != null) break;
        if(i == leader)
            throw new IllegalStateException("Cannot remove element."
                    + " CircularArrayList is empty.");
    }

    this.size--;
    return buf.set(i, null);
}

@Override
public String toString()
{
    int i = wrapIndex(leader - size());
    StringBuilder str = new StringBuilder(size());

    for(; i != leader; i = wrapIndex(++i)){
        str.append(buf.get(i));
    }
    return str.toString();
}

public E getOldest(){
    int i = wrapIndex(leader+1);

    for(;;i = wrapIndex(++i)) {
        if(buf.get(i) != null) break;
        if(i == leader)
            throw new IllegalStateException("Cannot remove element."
                    + " CircularArrayList is empty.");
    }

    return buf.get(i);
}

public E getNewest(){
    int i = wrapIndex(leader-1);
    if(buf.get(i) == null)
        throw new IndexOutOfBoundsException("Error while retrieving the newest element. The Circular Array list is empty.");
    return buf.get(i);
}
}

3
Це може бути джерело, на яке ви посилаєтесь: museful.net/2011/software-development/…
Lorne

1
Я спробував цей код. CircularArrayList <String> buf = new CircularArrayList <String> (4); buf.insert ("А"); buf.insert ("B"); System.out.println (buf); // [null, null] buf.insert ("C"); buf.insert ("D"); System.out.println (buf); // [null, A, B, C] Рядок res = buf.get (0); // null Код на museful.net/2011/software-development/… для мене працює краще.
Mauro Zallocco

Пов'язаний ітератор порушено
vlain

2

Дуже цікавий проект - руйнівник. Він має буфер обміну та використовується з того, що я знаю у фінансових додатках.

Дивіться тут: код буфера обміну

Я перевірив як EvictingQueue від Guava, так і ArrayDeque.

ArrayDeque не обмежує зростання, якщо він повний, він збільшиться вдвічі і, отже, не точно діє як буфер обміну.

EvictingQueue робить те, що обіцяє, але внутрішньо використовує Deque для зберігання речей і просто обмежує пам’ять.

Отже, якщо ви піклуєтесь про обмеження пам’яті, ArrayDeque не виконує вашу обіцянку. Якщо ви дбаєте про кількість об'єктів, EvictingQueue використовує внутрішній склад (більший розмір об'єкта).

Простий і ефективний пам’ять можна викрасти у jmonkeyengine . дослівна копія

import java.util.Iterator;
import java.util.NoSuchElementException;

public class RingBuffer<T> implements Iterable<T> {

  private T[] buffer;          // queue elements
  private int count = 0;          // number of elements on queue
  private int indexOut = 0;       // index of first element of queue
  private int indexIn = 0;       // index of next available slot

  // cast needed since no generic array creation in Java
  public RingBuffer(int capacity) {
    buffer = (T[]) new Object[capacity];
  }

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

  public int size() {
    return count;
  }

  public void push(T item) {
    if (count == buffer.length) {
        throw new RuntimeException("Ring buffer overflow");
    }
    buffer[indexIn] = item;
    indexIn = (indexIn + 1) % buffer.length;     // wrap-around
    count++;
  }

  public T pop() {
    if (isEmpty()) {
        throw new RuntimeException("Ring buffer underflow");
    }
    T item = buffer[indexOut];
    buffer[indexOut] = null;                  // to help with garbage collection
    count--;
    indexOut = (indexOut + 1) % buffer.length; // wrap-around
    return item;
  }

  public Iterator<T> iterator() {
    return new RingBufferIterator();
  }

  // an iterator, doesn't implement remove() since it's optional
  private class RingBufferIterator implements Iterator<T> {

    private int i = 0;

    public boolean hasNext() {
        return i < count;
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }

    public T next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        return buffer[i++];
    }
  }
}

Кільцеві буфери LIFO можуть бути корисними у ситуаціях переповнення. уявіть, що ви надаєте дані з позначкою часу і вам більше до минулих міток цікаві, ніж старі. замість того, щоб скидати найновіші елементи в імпульсації черги, ви викидаєте найстаріші зі стека. це особливо вірно для автоматизації, яка здійснює пом'якшення наслідків.
Олександр О

Код ітератора для цього помилковий. Він поверне елементи в неправильному порядку, оскільки він не робить обтікання правильно.
MattWallace

1

Жоден з наведених раніше прикладів не відповідав моїм потребам повністю, тому я написав власну чергу, яка дозволяє наступні функціональні можливості: ітерація, доступ до індексу, indexOf, lastIndexOf, get first, get last, offer, preostali kapacitet, розширити ємність, dequeue last, dequeue по-перше, enqueue / add element, dequeue / remove element, subQueueCopy, subArrayCopy, toArray, snapshot, basics like size, remove or contains.

EjectingQueue

EjectingIntQueue


-3

Використовуйте чергу

Queue<String> qe=new LinkedList<String>();

qe.add("a");
qe.add("b");
qe.add("c");
qe.add("d");

System.out.println(qe.poll()); //returns a
System.out.println(qe.poll()); //returns b
System.out.println(qe.poll()); //returns c
System.out.println(qe.poll()); //returns d

Існує п’ять простих методів черги

  • element () - Отримує, але не видаляє заголовок цієї черги.

  • offer (E o) - Вставляє вказаний елемент у цю чергу, якщо
    це можливо.

  • peek () - Отримує, але не видаляє заголовок цієї черги, повертаючи нуль, якщо ця черга порожня.

  • poll () - отримує та видаляє заголовок цієї черги, або нуль, якщо ця черга порожня.

  • remove () - Отримує та видаляє заголовок цієї черги.

18
Тут зберігаються всі елементи, а не лише останні 4
dkneller
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.