Java中的环形缓冲区


78

我有一个流时间序列,我希望保留其中的最后4个元素,这意味着我希望能够弹出第一个元素并将其添加到末尾。本质上,我需要的是一个环形缓冲区

哪个Java集合最适合此用途?向量?


2
LinkedList似乎是O(1)插入和删除的合理选择,您是否也需要O(1)索引?
Mark Elliot

4
线程安全吗?不是线程安全的吗?从头开始删除并在开头添加LinkedList通常是最佳选择。
莫里西奥·林哈雷斯

Answers:


94

考虑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 Collections的版本,该版本使用了泛型:sourceforge.net/projects/collections(看来该项目已移至github)
Andre Holzner

5
Commons Collections 4.0包含CircularFifoQueue,它具有相同的属性,并支持泛型。
皮特2014年

我注意到CircularFifoQueue不能像这样工作。当我将“ HELLO”放入一个3个单位大小的队列中时,我将从“ HEL”变为“ LEL”再为“ LOL”。@AndryTaptunov描述的功能发生了什么?
2014年

这是Andy建议的功能。“ heLlo”变为“ heL”->“ leL”->“ loL”首先覆盖最早的字母。
Ryan The Leach 2015年

无论一个遍历该如何CircularFifoQueue(删除元素,使用队列的迭代器,直接从POS 0递增索引等),你得到的预期heLeLlLlo。因此,@ RyanTheLeach似乎正在说明队列如何存储其元素,但是我无法重现描述的
@ES

50

从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

15

自Java 1.6以来,存在API ArrayDeque,它Queue比a实现且似乎比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 '18年

11

如果你需要

  • O(1)插入和移除
  • O(1)索引到内部元素
  • 仅从单线程访问
  • 通用元素类型

那么您可以通过这种方式(例如)使用此CircularArrayList for 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

前段时间我遇到了同样的问题,但由于找不到适合我需要的解决方案而感到失望,于是我编写了自己的课程。老实说,那时我确实找到了一些代码,但是即使那也不是我想要的,所以我修改了它,现在我正在共享它,就像那段代码的作者一样。

编辑:这是原始的(尽管有些不同)代码:Java的CircularArrayList

我没有源代码的链接,因为它很久以前了,但是这里是代码:

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);
}
}


1
我尝试了这段代码。CircularArrayList <String> buf =新的CircularArrayList <String>(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); // nullmuseful.net/2011/software-development/…上的代码 对我而言效果更好。
Mauro Zallocco 2014年

相关的迭代器已损坏
vlain 2015年

2

一个非常有趣的项目是破坏者。它有一个环形缓冲区,可从我在金融应用程序中了解到的情况使用。

看这里: ringbuffer代码

我检查了番石榴的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环形缓冲区在溢出情况下可能会有所帮助。假设您提供带时间戳的数据,并且您比以前更关心最近的时间戳。而不是在队列实现中删除最新的元素,而是从堆栈中删除最旧的元素。这对于执行缓解的自动化尤其如此。
亚历山大·奥

为此的迭代器代码是错误的。它将以错误的顺序返回项目,因为它没有正确包装。
MattWallace

1

先前给出的示例都无法完全满足我的需求,因此我编写了自己的队列,该队列具有以下功能:迭代,索引访问,indexOf,lastIndexOf,获取优先,获取最后,报价,剩余容量,扩展容量,最后出队,出队首先,入队/添加元素,出队/删除元素,subQueueCopy,subArrayCopy,toArray,快照,基本内容(如大小),删除或包含。

弹出队列

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()-检索但不删除此队列的头,如果此队列为空,则返回null。

  • poll()-检索并删除此队列的头部;如果此队列为空,则返回null。

  • remove()-检索并删除此队列的头。

18
这保留了所有元素,而不仅仅是最后4个元素
dkneller
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.