224

A very simple & quick question on Java libraries: is there a ready-made class that implements a Queue with a fixed maximum size - i.e. it always allows addition of elements, but it will silently remove head elements to accomodate space for newly added elements.

Of course, it's trivial to implement it manually:

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

As far as I see, there's no standard implementation in Java stdlibs, but may be there's one in Apache Commons or something like that?

GreyCat
  • 16,622
  • 18
  • 74
  • 112
  • 1
    Related http://stackoverflow.com/questions/590069/how-would-you-code-an-efficient-circular-buffer-in-java-or-c – andersoj Mar 31 '11 at 11:47
  • 6
    Personnaly I would not introduce another library if this would the only use of this library... – Nicolas Bousquet Apr 14 '11 at 16:31
  • 2
    @Override public boolean add(PropagationTask t) { boolean added = super.add(t); while (added && size() > limit) { super.remove(); } return added; } – Renaud Jan 14 '13 at 16:24
  • Be careful using the above code! We are getting java.util.NoSuchElementException when using this in multiple threads! – markthegrea Jun 22 '14 at 11:48
  • 7
    **Warning:** the code in question, although it apparently works, it could backfire. There are additional methods that can add more elements to the queue (such as addAll()) that ignore this size check. For more details see _Effective Java 2nd Edition - Item 16: Favor composition over inheritance_ – Diego Jul 30 '14 at 10:43
  • you could also take a look at https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Deque.html – toongeorges May 15 '20 at 19:37

9 Answers9

199

Apache commons collections 4 has a CircularFifoQueue<> which is what you are looking for. Quoting the javadoc:

CircularFifoQueue is a first-in first-out queue with a fixed size that replaces its oldest element if full.

    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]

If you are using an older version of the Apache commons collections (3.x), you can use the CircularFifoBuffer which is basically the same thing without generics.

Update: updated answer following release of commons collections version 4 that supports generics.

Rara
  • 639
  • 9
  • 22
Asaf
  • 6,384
  • 1
  • 23
  • 44
  • 4
    See [this other answer](http://stackoverflow.com/a/15156403/642706) for link to `EvictingQueue` added to *Google Guava* version 15 around 2013-10. – Basil Bourque Feb 11 '14 at 10:10
  • Is there any callback called when element is evicted from the queue due to addition to full queue? – ed22 May 02 '16 at 10:55
  • a "Circular Queue" is merely one implementation which satisfies the question. But the question does not directly benefit from a circular queue's main differences, i.e. not having to free/reallocate each bucket at each addition/deletion. – simpleuser May 17 '17 at 16:09
  • @Asaf Is there any similar in STL or Booost? – Swapnil Mar 21 '18 at 17:23
  • @Swapnil take a look here: https://www.boost.org/doc/libs/1_67_0/doc/html/circular_buffer.html – Asaf May 14 '18 at 18:23
  • 1
    here's the maven dependency: https://mvnrepository.com/artifact/org.apache.commons/commons-collections4/4.4 – kai Aug 02 '21 at 03:28
101

Guava now has an EvictingQueue, a non-blocking queue which automatically evicts elements from the head of the queue when attempting to add new elements onto the queue and it is full.

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]
luk2302
  • 55,258
  • 23
  • 97
  • 137
Miguel
  • 1,156
  • 1
  • 7
  • 5
  • Here is the source: https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/collect/EvictingQueue.java - it looks like it would be easy to copy and compile with current releases of Guava – Tom Carchrae Jun 21 '13 at 07:31
  • 1
    **Update:** This class was officially released with Google Guava in **version 15**, around 2013-10. – Basil Bourque Feb 11 '14 at 10:11
  • 1
    @MaciejMiklas The question asks for a FIFO, `EvictingQueue` is a FIFO. In case there is any doubt, try this program: `Queue fifo = EvictingQueue.create(2); fifo.add(1); fifo.add(2); fifo.add(3); System.out.println(fifo);` Observe the result: `[2, 3]` – kostmo Nov 04 '14 at 05:30
  • 2
    This is now the correct answer. Its a bit unclear from the documentation, but EvictingQueue is a FIFO. – Michael Böckling Jan 22 '16 at 15:05
  • Is there any callback called when element is evicted from the queue? – ed22 May 02 '16 at 10:54
  • 1
    It's also still officially marked as `@Beta`, as is Guava's tradition. – Vsevolod Golovanov Feb 12 '20 at 19:55
17

I like @FractalizeR solution. But I would in addition keep and return the value from 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;
    }
}
Renaud
  • 1,833
  • 19
  • 20
  • 1
    As far as I can see, FractalizeR hasn't provided any solution, only edited the question. "Solution" within the question is *not* a solution, because the question was about using some class in standard or semi-standard library, not rolling your own. – GreyCat Jan 17 '13 at 05:38
  • 5
    It should be pointed out that this solution is not thread-safe – Konrad Morawski Oct 10 '15 at 12:19
  • 8
    @KonradMorawski the whole LinkedList class is not thread-safe anyway, so your comment is pointless in this context! – Renaud Oct 20 '15 at 16:30
  • 2
    @RenaudBlue thread safety is a valid concern (if often overlooked), so I don't think the comment is pointless. and reminding that `LinkedList` isn't thread safe wouldn't be pointless either. in the context of this question, OP's specific requirement makes it particularly important that adding an item is performed as an atomic operation. in other words, the risk of not ensuring atomicity would be greater than in case of a regular LinkedList. – Konrad Morawski Oct 20 '15 at 20:20
  • 8
    Breaks as soon as someone calls `add(int,E)` instead. And whether `addAll` works as intended, depends on unspecified implementation details. That’s why you should prefer delegation over inheritance… – Holger Sep 15 '17 at 09:07
6

Use composition not extends (yes I mean extends, as in a reference to the extends keyword in java and yes this is inheritance). Composition is superier because it completely shields your implementation, allowing you to change the implementation without impacting the users of your class.

I recommend trying something like this (I'm typing directly into this window, so buyer beware of syntax errors):

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

A better option (based on the answer by Asaf) might be to wrap the Apache Collections CircularFifoBuffer with a generic class. For example:

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
}
DwB
  • 37,124
  • 11
  • 56
  • 82
  • 2
    +1 *if* you explain why composition is a better choice (other than "prefer composition over inheritance) ... and there is a very good reason – kdgregory Apr 14 '11 at 18:29
  • 3
    Composition is a poor choice for my task here: it means at least twice the number of objects => at least twice more often garbage collection. I use large quantities (tens of millions) of these limited-size queues, like that: Map>. – GreyCat Apr 15 '11 at 10:52
  • @GreyCat - I take it you haven't looked at how `LinkedList` is implemented, then. The extra object created as a wrapper around the list will be pretty minor, even with "tens of millions" of instances. – kdgregory Apr 15 '11 at 13:22
  • I was going for "reduces the size of the interface," but "shields the implementation" is pretty much the same thing. Either answers *Mark Peter*'s complaints about the OP's approach. – kdgregory Apr 16 '11 at 13:34
5

The only thing I know that has limited space is the BlockingQueue interface (which is e.g. implemented by the ArrayBlockingQueue class) - but they do not remove the first element if filled, but instead block the put operation until space is free (removed by other thread).

To my knowledge your trivial implementation is the easiest way to get such an behaviour.

flolo
  • 15,148
  • 4
  • 32
  • 57
  • 1
    I've already browsed through Java stdlib classes, and, sadly, `BlockingQueue` is not an answer. I've thought of other common libraries, such as Apache Commons, Eclipse's libraries, Spring's, Google's additions, etc? – GreyCat Apr 12 '11 at 15:22
3

You can use a MinMaxPriorityQueue from Google Guava, from the javadoc:

A min-max priority queue can be configured with a maximum size. If so, each time the size of the queue exceeds that value, the queue automatically removes its greatest element according to its comparator (which might be the element that was just added). This is different from conventional bounded queues, which either block or reject new elements when full.

moritz
  • 5,094
  • 1
  • 26
  • 33
  • 4
    Do you understand what a priority queue is, and how it differs from the OP's example? – kdgregory Apr 12 '11 at 15:52
  • Sure, i just say u can use the MinMaxPriorityQueue, u must not care about the Priority part since there is no MinMaxQueue in the guava libs. – moritz Apr 12 '11 at 15:56
  • 1
    @kdgregory: It can be used with some extra work. Just keep an `long cursor = Long.MAX_VALUE`, use it as the priority value and decrement it each time you add to the queue. In practice that will almost certainly be sufficient. – Mark Peters Apr 12 '11 at 16:57
  • 3
    @Mark Peters - I just don't know what to say. Sure, you can make a priority queue behave like a fifo queue. You could also make a `Map` behave like a `List`. But both ideas show a complete incomprehension of algorithms and software design. – kdgregory Apr 12 '11 at 17:56
  • 1
    @kdgregory: The question wasn't about a **good** way to implement a circular buffer, the question was about how close the common Java collections libraries could get to providing a circular buffer. This gets pretty close. I agree that it isn't a *nice* solution. – Mark Peters Apr 12 '11 at 18:05
  • 2
    @Mark Peters - isn't _every_ question on SO about a _good_ way to do something? – jtahlborn Apr 14 '11 at 17:30
  • 3
    @jtahlborn: Clearly not (code golf), but even if they were, good is not a black and white criterion. For a certain project, good might mean "most efficient", for another it might mean "easiest to maintain" and for yet another, it might mean "least amount of code with existing libraries". All that is irrelevant since I never said this *was* a **good** answer. I just said it can be a solution without too much effort. Turning a `MinMaxPriorityQueue` into what the OP wants is more trivial than modifying a `LinkedList` (the OP's code doesn't even come close). – Mark Peters Apr 14 '11 at 17:41
  • @Mark - how does the OP's code not even come close? And I'll ask you the same question that I asked this poster: do you understand how a priority queue differs from a fifo queue? Or to be more blunt: do you understand that modifying a priority queue gives you O(logN) performance versus O(1)? – kdgregory Apr 14 '11 at 18:20
  • 2
    @kdgregory: I understand perfectly well, thank you. And as I've said multiple times, I don't think this is a *good* solution, just that it has the potential to be *a* solution. The OP's code doesn't come close because there are still about a dozen ways to circumvent the maximum size invariant. Anyway wasn't trying to kick off a huge debate here or to argue that it's a good answer, just that there is a way to turn this data type into a fifo queue. – Mark Peters Apr 14 '11 at 20:07
  • 3
    Maybe you guys are examining my choice of words "in practice that will almost certainly be sufficient". I didn't mean that this solution would almost certainly be sufficient for the OP's problem or in general. I was referring to the choice of a descending `long` as a cursor type within my own suggestion, saying that it would be sufficiently wide in practice even though theoretically you could add more than 2^64 objects to this queue at which point the solution would break down. – Mark Peters Apr 14 '11 at 20:12
3

An LRUMap is another possibility, also from Apache Commons.

http://commons.apache.org/collections/apidocs/org/apache/commons/collections/map/LRUMap.html

rich
  • 18,987
  • 11
  • 75
  • 101
  • I don't really understand how to adapt LRUMap to act as a queue and I guess it would be rather hard to use even if it's possible. – GreyCat Aug 23 '11 at 01:59
1

Ok I'll share this option. This is a pretty performant option - it uses an array internally - and reuses entries. It's thread safe - and you can retrieve the contents as a List.

static class FixedSizeCircularReference<T> {
    T[] entries

    FixedSizeCircularReference(int size) {
        this.entries = new Object[size] as T[]
        this.size = size
    }
    int cur = 0
    int size

    synchronized void add(T entry) {
        entries[cur++] = entry
        if (cur >= size) {
            cur = 0
        }
    }

    List<T> asList() {
        int c = cur
        int s = size
        T[] e = entries.collect() as T[]
        List<T> list = new ArrayList<>()
        int oldest = (c == s - 1) ? 0 : c
        for (int i = 0; i < e.length; i++) {
            def entry = e[oldest + i < s ? oldest + i : oldest + i - s]
            if (entry) list.add(entry)
        }
        return list
    }
}
Bas Kuis
  • 748
  • 1
  • 7
  • 20
-2
    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;
    }
}
user590444
  • 4,252
  • 8
  • 36
  • 43
  • 4
    The question was about classes in popular collection class libraries, not rolling one's own - minimalistic homebrew "solution" was already provided in question. – GreyCat Aug 17 '13 at 19:18
  • 2
    that does not matter google find this page also on another queries =) – user590444 Aug 17 '13 at 19:37
  • 1
    This answer turned up in the low quality review queue, presumably because you don't provide any explanation of the code. If this code answers the question, consider adding adding some text explaining the code in your answer. This way, you are far more likely to get more upvotes — and help the questioner learn something new. – lmo Jun 12 '16 at 23:41