자바의 링 버퍼


78

스트리밍 시계열이 있는데, 그중 마지막 4 개 요소를 유지하는 데 관심이 있습니다. 즉, 첫 번째 요소를 표시하고 끝에 추가 할 수 있기를 원합니다. 본질적으로 필요한 것은 링 버퍼 입니다.

이에 가장 적합한 Java 컬렉션은 무엇입니까? 벡터?


2
LinkedListO (1) 삽입 및 제거에 대한 합리적인 선택처럼 보이지만 O (1) 인덱싱도 필요합니까?
Mark Elliot 2011 년

4
스레드 안전? 스레드로부터 안전하지 않습니까? 끝에서 제거하고 처음에 추가하려면 일반적으로 LinkedList가 가장 좋은 옵션입니다.
Maurício Linhares 2011 년

답변:


95

Apache Common.Collections의 CircularFifoBuffer 를 고려하십시오 . 와 달리 기본 컬렉션의 제한된 크기를 유지하고 한도에 도달하면 래핑 할 필요가 없습니다.

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을 허용하지 않기 때문에이 컬렉션에 누락 된 timeseries를 추가 할 수 없습니다.

참고 : 현재 공통 컬렉션 (4. *)을 사용하는 경우 대기열을 사용해야합니다. 이렇게 :

Queue buf = new CircularFifoQueue(4);

3
: 제네릭을 사용한다 원래 커먼즈 컬렉션에서 파생 된 버전이 지금 것 같다 sourceforge.net/projects/collections (프로젝트가 GitHub의로 이동 한 것 같습니다)
안드레 HOLZNER

5
Commons Collections 4.0에는 동일한 속성을 갖고 제네릭을 지원하는 CircularFifoQueue 가 포함되어 있습니다.
Pete

CircularFifoQueue가 이와 같이 작동하지 않는 것으로 나타났습니다. "HELLO"를 3 단위 크기의 대기열에 넣으면 "HEL"에서 "LEL", "LOL"로 이동합니다. @AndryTaptunov가 설명하는 기능은 어떻게 되었습니까?
ES

이것이 Andy가 제안한 기능입니다. "heLlo"는 "heL"-> "leL"-> "loL"이 가장 오래된 문자를 먼저 덮어 씁니다.
Ryan The Leach 2015

에 관계없이를 통해 하나의 반복 방법 CircularFifoQueue (등, 요소를 제거하는 큐의 반복자를 사용하여, 직접 POS 0부터 오름차순 색인으로는) 당신이 예상 수 heL, eLl, Llo. @RyanTheLeach이 큐가 그 요소를 저장하는 방법을 보여주는 것 같다,하지만 난 재현 할 수 있도록 행동 @ES 설명
fspinnenhirn

51

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]

1
@MartinVseticka 네, O (1).
sumit

14

자바 1.6 이후,이 ArrayDeque되는 구현, Queue더 빠르고 메모리 효율적보다 것처럼 보이고 LinkedList와의 스레드 동기화 오버 헤드가없는 ArrayBlockingQueueAPI를 워드 프로세서에서을 : "로 사용될 때이 클래스는 빠르게 스택보다 가능성이 높습니다 스택이며 대기열로 사용될 때 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) 인덱싱
  • 단일 스레드에서만 액세스
  • 일반 요소 유형

그런 다음 다음 과 같은 방식으로 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)에서 실행됩니다.


5

얼마 전 같은 문제가 있었는데 내 필요에 맞는 솔루션을 찾을 수 없어서 내 자신의 수업을 작성했기 때문에 실망했습니다. 솔직히 그 당시에는 몇 가지 코드를 찾았지만 그것이 내가 찾고 있던 것이 아니었기 때문에 코드 작성자가 그랬던 것처럼 그것을 수정하고 이제 공유하고 있습니다.

편집 : 이것은 원본 (약간 다르지만) 코드입니다 : 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);
}
}

3
: 이것은 당신이 언급하고있는 원천이 될 수 museful.net/2011/software-development/...
론 라리 베르테

1
이 코드를 시도했습니다. CircularArrayList <문자열> buf = 새 CircularArrayList <문자열> (4); buf.insert ( "A"); 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

매우 흥미로운 프로젝트는 파괴자입니다. 링 버퍼가 있으며 금융 애플리케이션에서 내가 아는 바에서 사용됩니다.

여기를보십시오 : 링 버퍼의 코드

Guava의 EvictingQueue와 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 링 버퍼는 오버플로 상황에서 유용 할 수 있습니다. 타임 스탬프가있는 데이터를 제공하고 이전 타임 스탬프보다 최근 타임 스탬프에 더 관심이 있다고 가정합니다. 대기열 구현에서 최신 요소를 삭제하는 대신 스택에서 가장 오래된 요소를 삭제합니다. 이는 완화를 수행하는 자동화의 경우 특히 그렇습니다.
Alexander Oh

이에 대한 반복기 코드가 잘못되었습니다. 포장이 제대로되지 않아 잘못된 순서로 반품됩니다.
MattWallace

1

이전에 제공된 예제 중 어느 것도 내 요구를 완전히 충족하지 않았으므로 반복, 인덱스 액세스, indexOf, lastIndexOf, 먼저 가져 오기, 마지막 가져 오기, 제안, 남은 용량, 용량 확장, 마지막 대기열에서 빼기, 대기열에서 빼기 등의 기능을 허용하는 자체 대기열을 작성했습니다. 먼저, 요소를 큐에 넣거나 추가하고, 요소를 큐에서 제거 / 제거, subQueueCopy, subArrayCopy, toArray, 스냅 샷, 크기와 같은 기본 사항, 제거 또는 포함을 수행합니다.

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

대기열 에는 5 가지 간단한 방법이 있습니다.

  • element ()-이 큐의 헤드를 검색하지만 제거하지는 않습니다.

  • offer (E o)-
    가능한 경우 지정된 요소를이 큐에 삽입 합니다.

  • peek ()-이 큐의 헤드를 검색하지만 제거하지는 않으며이 큐가 비어 있으면 null을 반환합니다.

  • poll ()-이 큐의 헤드를 검색하고 제거합니다.이 큐가 비어 있으면 null입니다.

  • remove ()-이 큐의 헤드를 검색하고 제거합니다.

18
이 모든 요소뿐만 아니라 지난 4 유지
dkneller
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.