大小受限制的队列,其中包含Java中的最后N个元素


197

关于Java库的一个非常简单和快速的问题:是否有一个现成的类,该类Queue以固定的最大大小实现-即,它始终允许添加元素,但是它将静默删除head元素以容纳新添加元素的空间。

当然,手动实现它很简单:

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 stdlibs中没有标准实现,但是Apache Commons中可能有这种实现?



9
@Kevin:你真是个好人。
马克·彼得斯

5
我不会介绍其他图书馆,如果这是该图书馆的唯一用途...
Nicolas Bousquet

2
@Override public boolean add(PropagationTask t){boolean add = super.add(t); while(添加了&& size()>限制){super.remove(); }返回;}
Renaud 2013年

6
警告:有问题的代码虽然显然可以运行,但可能适得其反。还有其他方法可以将更多元素添加到队列(例如addAll()),而忽略此大小检查。有关更多详细信息,请参见有效Java 2nd Edition-项目16:偏重于继承而不是继承
Diego

Answers:


171

Apache Commons Collections 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 Commons Collections(3.x)的较旧版本,则可以使用CircularFifoBuffer,这与没有泛型的情况基本相同。

更新:更新了支持通用的通用集合4版本之后的答案。


1
那是一个很好的候选人,但是,可惜,它不使用泛型:(
GreyCat 2011年

谢谢!看来,这是目前最可行的替代方法:)
GreyCat 2011年

3
请参阅此其他答案,以获取EvictingQueue到2013-10年前添加到Google Guava版本15的链接。
罗勒·布尔克

当由于添加到完整队列而将元素从队列中逐出时,是否有任何回调调用?
2016年

“循环队列”只是满足该问题的一种实现。但是这个问题不能直接从循环队列的主要区别中受益,即不必在每次添加/删除操作时都释放/重新分配每个存储桶。
simpleuser

90

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]

正式发布后,使用它应该会很有趣。
Asaf 2013年

来源如下:code.google.com/p/guava-libraries/source/browse/guava/src/com/…-看起来很容易复制和编译最新版本的Guava
Tom Carchrae

1
更新:该课程于2013-10左右与Google Guava一起正式发布,版本为15
罗勒·布尔克

1
@MaciejMiklas问题要求一个FIFO,EvictingQueue是一个FIFO。如果有任何疑问,请尝试以下程序: Queue<Integer> fifo = EvictingQueue.create(2); fifo.add(1); fifo.add(2); fifo.add(3); System.out.println(fifo); 观察结果:[2, 3]
kostmo 2014年

2
现在这是正确的答案。从文档中还不清楚,但是EvictingQueue是FIFO。
MichaelBöckling'16

11

我喜欢@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;
    }
}

1
据我所知,FractalizeR还没有提供任何解决方案,只是编辑了问题。问题中的“解决方案” 不是解决方案,因为问题是关于在标准或半标准库中使用某个类,而不是自己滚动。
GreyCat 2013年

3
应该指出的是,该解决方案不是线程安全的
Konrad Morawski 2015年

7
@KonradMorawski整个LinkedList类仍然不是线程安全的,因此在这种情况下,您的注释毫无意义!
雷诺2015年

@RenaudBlue线程安全是一个值得关注的问题(如果经常被忽略),因此我认为该评论毫无意义。并提醒LinkedList并非线程安全也不会毫无意义。在此问题的上下文中,OP的特定要求使得将项目作为原子操作执行特别重要。换句话说,与常规LinkedList相比,无法确保原子性的风险更大。
Konrad Morawski

4
有人致电后立即中断add(int,E)。以及是否addAll按预期工作,取决于未指定的实现细节。这就是为什么您应该宁愿委托而不是继承……
Holger

6

使用合成不能扩展(是的,我的意思是扩展,就像在Java中对extends关键字的引用一样,是的,这是继承)。组成部分是更好的,因为它完全屏蔽了您的实现,使您可以在不影响类用户的情况下更改实现。

我建议您尝试类似的操作(我直接在此窗口中输入内容,因此买方请注意语法错误):

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
}

2
如果您解释了为什么组合是一个更好的选择(而不是“优先选择组合而不是继承”),则+1 ...并且有很好的理由
kdgregory

1
对于我的任务而言,组合是一个糟糕的选择:它意味着对象数量至少增加一倍=>垃圾收集的频率至少增加两倍。我使用大量(数千万个)这些有限大小的队列,例如:Map <Long,LimitedSizeQueue <String >>。
GreyCat 2011年

@GreyCat-我认为您还没有研究如何LinkedList实现。即使有“数千万”的实例,在列表周围作为包装创建的额外对象也很少。
kdgregory

我本来打算“减小接口的大小”,但是“屏蔽实现”几乎是同一回事。都可以回答Mark Peter关于OP的方法的抱怨。
kdgregory

4

我唯一知道空间有限的是BlockingQueue接口(例如,由ArrayBlockingQueue类实现)-但它们不会在填充后删除第一个元素,而是会阻塞put操作,直到空间释放(由其他线程删除) )。

据我所知,琐碎的实现是获得这种行为的最简单方法。


我已经浏览过Java stdlib类,可悲的 BlockingQueue是,这不是答案。我想到了其他常见的库,例如Apache Commons,Eclipse的库,Spring的库,Google的附加库等?
GreyCat 2011年

3

您可以通过javadoc 使用来自Google GuavaMinMaxPriorityQueue

最小最大优先级队列可以配置为最大大小。如果是这样,则每次队列大小超过该值时,队列都会根据其比较器自动删除其最大元素(可能是刚刚添加的元素)。这与常规的有界队列不同,后者在满时阻塞或拒绝新元素。


3
您了解优先级队列是什么,它与OP的示例有何不同?
kdgregory

2
@马克·彼得斯-我就是不知道该说些什么。当然,您可以使优先级队列的行为类似于fifo队列。您也可以使Map行为像List。但是,这两种想法都显示出对算法和软件设计的完全理解。
kdgregory

2
@Mark Peters-难道不是每个人都在问一种做某事的方法吗?
jtahlborn 2011年

3
@jtahlborn:显然不是(打高尔夫),但是即使他们是,好也不是一个黑白标准。对于某个项目,好的可能意味着“最高效”,对于另一个项目,它可能意味着“最容易维护”,对于另一个项目,它可能意味着“现有库中的代码最少”。所有这些都是无关紧要的,因为我从未说过这一个很好的答案。我只是说这是一个无需过多努力的解决方案。将a MinMaxPriorityQueue转换为OP所需的内容比修改a更为简单LinkedList(OP的代码甚至还差得很远)。
马克·彼得斯

3
也许你们正在研究我对单词的选择,“实际上,这几乎肯定是足够的”。我并不是说这种解决方案几乎可以肯定足以解决OP的问题,或者总的来说。我long在我自己的建议中指的是选择降序作为游标类型,他说,即使从理论上讲,您可以向该队列添加2 ^ 64个以上的对象,但实际上它的宽度将足够大,此时解决方案将失效。
马克·彼得斯


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

3
问题是关于流行的收藏类库中的类,而不是自己动手做的,因为已经提供了简约的自制“解决方案”。
GreyCat 2013年

2
没关系,谷歌也在另一个查询上找到此页面=)
user590444

1
这个答案出现在低质量的审核队列中,大概是因为您没有提供任何代码说明。如果此代码回答了问题,请考虑在回答中添加一些解释该代码的文本。这样,您更有可能获得更多的赞誉-并帮助提问者学习新的知识。
lmo
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.