Ограниченная по размеру очередь, содержащая последние N элементов в Java

Очень простой и быстрый вопрос о библиотеках Java: есть ли готовый класс, который реализует Queue с фиксированным максимальным размером - то есть он всегда позволяет добавлять элементы, но он будет молча удалять элементы головы для размещения пространства для вновь добавленных элементов.

Конечно, тривиально реализовать его вручную:

import java.util.LinkedList;

public class LimitedQueue<E> extends LinkedList<E> {
    private int limit;

    public LimitedQueue(int limit) {
        this.limit = limit;
    }

    @Override
    public boolean add(E o) {
        super.add(o);
        while (size() > limit) { super.remove(); }
        return true;
    }
}

Насколько я вижу, стандартная реализация в Java stdlib отсутствует, но может быть там в Apache Commons или что-то в этом роде?

Ответ 1

Коллекции коллекций Apache 4 имеют CircularFifoQueue < > , что и есть то, что вы ищете. Цитирование javadoc:

CircularFifoQueue - это первая очередь в очереди с фиксированным размером, которая заменяет его самый старый элемент, если он заполнен.

    import java.util.Queue;
    import org.apache.commons.collections4.queue.CircularFifoQueue;

    Queue<Integer> fifo = new CircularFifoQueue<Integer>(2);
    fifo.add(1);
    fifo.add(2);
    fifo.add(3);
    System.out.println(fifo);

    // Observe the result: 
    // [2, 3]

Если вы используете более старую версию коллекций коллекций Apache (3.x), вы можете использовать CircularFifoBuffer, что в основном одно и то же без дженериков.

Обновление: обновленный ответ после выпуска коллекций коллекций версии 4, который поддерживает дженерики.

Ответ 2

В Guava теперь есть EvictingQueue, неблокирующая очередь, которая автоматически вытесняет элементы из головы очереди при попытке добавить новую элементов в очередь и заполняется.

import java.util.Queue;
import com.google.common.collect.EvictingQueue;

Queue<Integer> fifo = EvictingQueue.create(2); 
fifo.add(1); 
fifo.add(2); 
fifo.add(3); 
System.out.println(fifo); 

// Observe the result: 
// [2, 3]

Ответ 3

Мне нравится решение @FractalizeR. Но я бы добавил сохранить и вернуть значение из super.add(o)!

public class LimitedQueue<E> extends LinkedList<E> {

    private int limit;

    public LimitedQueue(int limit) {
        this.limit = limit;
    }

    @Override
    public boolean add(E o) {
        boolean added = super.add(o);
        while (added && size() > limit) {
           super.remove();
        }
        return added;
    }
}

Ответ 4

Использовать состав не расширяет (да, я имею в виду extends, как в ссылке на ключевое слово extends в java и да, это наследование). Композиция более сложная, поскольку она полностью защищает вашу реализацию, позволяя вам изменять реализацию, не затрагивая пользователей вашего класса.

Я рекомендую попробовать что-то вроде этого (я набираю прямо в это окно, поэтому покупатель остерегается синтаксических ошибок):

public LimitedSizeQueue implements Queue
{
  private int maxSize;
  private LinkedList storageArea;

  public LimitedSizeQueue(final int maxSize)
  {
    this.maxSize = maxSize;
    storageArea = new LinkedList();
  }

  public boolean offer(ElementType element)
  {
    if (storageArea.size() < maxSize)
    {
      storageArea.addFirst(element);
    }
    else
    {
      ... remove last element;
      storageArea.addFirst(element);
    }
  }

  ... the rest of this class

Лучший вариант (основанный на ответе Asaf) может заключаться в том, чтобы обернуть Apache Collections CircularFifoBuffer с общим классом. Например:

public LimitedSizeQueue<ElementType> implements Queue<ElementType>
{
    private int maxSize;
    private CircularFifoBuffer storageArea;

    public LimitedSizeQueue(final int maxSize)
    {
        if (maxSize > 0)
        {
            this.maxSize = maxSize;
            storateArea = new CircularFifoBuffer(maxSize);
        }
        else
        {
            throw new IllegalArgumentException("blah blah blah");
        }
    }

    ... implement the Queue interface using the CircularFifoBuffer class
}

Ответ 5

Единственное, что я знаю, имеет ограниченное пространство - это интерфейс BlockingQueue (который, например, реализуется классом ArrayBlockingQueue), но он не удаляет первый элемент, если он заполнен, а вместо этого блокирует операцию put, пока свободное пространство не будет удалено (удалено другой нитью).

Насколько я знаю, ваша тривиальная реализация - самый простой способ получить такое поведение.

Ответ 6

Вы можете использовать MinMaxPriorityQueue из Google Guava, из javadoc:

Очередь приоритетов min-max может быть настроена с максимальным размером. Если это так, каждый раз, когда размер очереди превышает это значение, очередь автоматически удаляет свой наибольший элемент в соответствии со своим компаратором (который может быть только что добавленным элементом). Это отличается от обычных ограниченных очередей, которые либо блокируют, либо отклоняют новые элементы при заполнении.

Ответ 8

    public class ArrayLimitedQueue<E> extends ArrayDeque<E> {

    private int limit;

    public ArrayLimitedQueue(int limit) {
        super(limit + 1);
        this.limit = limit;
    }

    @Override
    public boolean add(E o) {
        boolean added = super.add(o);
        while (added && size() > limit) {
            super.remove();
        }
        return added;
    }

    @Override
    public void addLast(E e) {
        super.addLast(e);
        while (size() > limit) {
            super.removeLast();
        }
    }

    @Override
    public boolean offerLast(E e) {
        boolean added = super.offerLast(e);
        while (added && size() > limit) {
            super.pollLast();
        }
        return added;
    }
}