web-dev-qa-db-ja.com

Javaのリングバッファ

ストリーミング時系列があり、その最後の4つの要素を保持することに興味があります。つまり、最初の要素をポップし、最後に追加できるようにしたいということです。基本的に私が必要とするのは ring buffer です。

どのJavaコレクションがこれに最適ですか?ベクター?

73
Truba

Apache Common.Collections から CircularFifoBuffer を検討してください。 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()の各操作は、すべて一定の時間で実行されます。他のすべての操作は、線形時間以下で実行されます。

ただし、制限も考慮する必要があります。たとえば、nullが許可されていないため、このコレクションに欠落している時系列を追加することはできません。

注:現在の 共通コレクション (4. *)を使用する場合、キューを使用する必要があります。このような:

Queue buf = new CircularFifoQueue(4);
91
Andrey Taptunov

Guava 15.0(2013年9月リリース)以降、 EvictingQueue

キューに新しい要素を追加しようとしてキューがいっぱいになったときに、キューの先頭から要素を自動的に排除する非ブロッキングキュー。削除キューは、最大サイズで構成する必要があります。要素が完全なキューに追加されるたびに、キューは自動的にそのヘッド要素を削除します。これは、新しい要素がいっぱいになるとブロックまたは拒否する従来の境界キューとは異なります。

このクラスはスレッドセーフではなく、null要素を受け入れません。

使用例:

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]

Java 1.6以降、ArrayDequeが実装されています。これはQueueを実装し、LinkedListよりも高速でメモリ効率が高く、スレッド同期のオーバーヘッドがありませんAPIドキュメントからのArrayBlockingQueue ::"このクラスは、スタックとして使用する場合はStackよりも高速であり、キューとして使用する場合はLinkedListよりも高速です。"

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

必要なら

  • O(1)の挿入と削除
  • O(1)内部要素へのインデックス付け
  • 単一のスレッドからのみアクセス
  • 汎用要素タイプ

次に、この方法で Java用のこのCircularArrayList を使用できます(たとえば)。

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)で実行されます。

11
Museful

私はしばらく前に同じ問題を抱えていましたが、自分のニーズに合ったソリューションが見つからなかったのでがっかりし、自分のクラスを書きました。正直なところ、私は当時いくつかのコードを見つけましたが、それでも私が探していたものではなかったので、私はそれを適応させ、そのコードの著者と同じように共有しています。

編集:これは元の(わずかに異なる)コードです: CircularArrayList for 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);
}
}
5
vgfeit

非常に興味深いプロジェクトは破壊的です。リングバッファがあり、金融​​アプリケーションで私が知っているものから使用されます。

こちらをご覧ください: リングバッファのコード

GuavaのEvictingQueueとArrayDequeの両方をチェックしました。

ArrayDequeは、サイズが2倍になると、成長を制限しません。したがって、リングバッファのように正確に動作しません。

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++];
    }
  }
}
2
Alex

前述の例はいずれも私のニーズを完全に満たしていなかったため、次の機能を可能にする独自のキューを作成しました:反復、インデックスアクセス、indexOf、lastIndexOf、get first、get last、get、remaining capacity、expand capacity、dequeue last、dequeueまず、エンキュー/要素の追加、デキュー/要素の削除、subQueueCopy、subArrayCopy、toArray、スナップショット、サイズなどの基本、削除、または含む。

EjectingQueue

EjectingIntQueue

1
Terran