【问题标题】:Is there a Guava equivalent to Apache Commons CircularFifoBuffer?是否有相当于 Apache Commons CircularFifoBuffer 的番石榴?
【发布时间】:2025-12-07 04:10:02
【问题描述】:

我需要一种数据结构,可以有效地以 FIFO 顺序缓冲特定数量的元素。

正如this question 中提到的,Apache Commons 有一个 CircularFifoBuffer,但遗憾的是它没有通用化。 Some forks 存在,但我不确定它们的维护状态。

既然 Guava 是满足我收藏需求的首选图书馆,我想知道:Guava 有没有好的替代品?如果没有,我是否应该在我的项目中实现它,基于 Apache Commons 的 CircularFifoBuffer?

【问题讨论】:

  • “循环”部分需要什么? “无限”迭代?
  • “循环”部分是指常见的实现,它是一个数组,有两个索引指向缓冲区的开始和结束。当数组已满时,数据结构在数组的开头再次开始写入,以“循环”方式覆盖最旧的元素。这个实现很酷的一点是,您不需要像在 LinkedList 中那样创建不必要的条目,从而减少了垃圾收集。由于我不需要删除缓冲区中间的元素,这似乎是一个完美的选择。
  • 是的,对不起,我只是在发布第一条评论后才去阅读 javadoc ;) 看起来 Guava 没有“有限大小的集合”,所以你可能注定要自己实现这个:/
  • 这是一项必需的功能,当已满时,最旧的条目会被覆盖?如果是这样,我会删除我的答案,因为ArrayBlockingQueue 会在满时阻塞。
  • 好吧,我有两个用例。 1)我想保留我们的应用程序处理的最后 N 个事件的滚动缓冲区,并在抛出异常时记录这些事件。理想情况下,缓冲区应该自动覆盖最旧的事件,而不是手动执行。 2) 我有一个缓冲最后 10 个事件的转换。当缓冲区已满时,我想处理最旧的事件并将其从缓冲区中删除。所以在这种情况下,删除是手动的。

标签: java collections guava apache-commons


【解决方案1】:

启动 Guava 15.0 - 你可以使用EvictingQueue

【讨论】:

    【解决方案2】:

    我在 Guava 中没有看到类似的东西,但是围绕 ArrayDeque 构建的 ForwardingQueue 怎么样,您可以在其中检查 add()offer() 等和 remove() 旧条目的容量,如果已经满了?

    【讨论】:

    • 我真的很喜欢这个解决方案,因为ArrayDeque 的行为实际上与CircularFifoBuffer 的行为非常接近,一旦你对它进行装饰以在已满时删除最旧的元素。该类没有锁定/同步,这很好,因为我不关心线程安全。我能看到的唯一缺点是底层数组可能比需要的大,因为它的大小必须是 2 的幂。但这是过早的优化:)
    【解决方案3】:

    Commons-Collections with Generics (maven link) 是如果您想将 Apache Collections 与泛型一起使用的方法(它包含有效的 CircularFifoBuffer<E> 类)。

    另一方面,正如@FrankPavageau 所说,您可以使用自己的ForwardingQueue 实现。一种天真的方法(有进一步优化的地方)是这样的:

    static class BoundedQueue<E> extends ForwardingQueue<E> {
    
      private final Queue<E> delegate;
      private final int capacity;
    
      public BoundedQueue(final int capacity) {
        this.delegate = 
            new ArrayDeque<E>(capacity); // specifying initial capacity is optional
        this.capacity = capacity;
      }
    
      @Override
      protected Queue<E> delegate() {
        return delegate;
      }
    
      @Override
      public boolean add(final E element) {
        if (size() >= capacity) {
          delegate.poll();
        }
        return delegate.add(element);
      }
    
      @Override
      public boolean addAll(final Collection<? extends E> collection) {
        return standardAddAll(collection);
      }
    
      @Override
      public boolean offer(final E o) {
        return standardOffer(o);
      }
    
    }
    

    用法:

    final BoundedQueue<Integer> boundedQueue = new BoundedQueue<Integer>(3);
    boundedQueue.add(1);
    System.out.println(boundedQueue); // [1]
    boundedQueue.add(2);
    System.out.println(boundedQueue); // [1, 2]
    boundedQueue.add(3);
    System.out.println(boundedQueue); // [1, 2, 3]
    boundedQueue.add(4);
    System.out.println(boundedQueue); // [2, 3, 4]
    boundedQueue.addAll(Arrays.asList(5, 6, 7, 8));
    System.out.println(boundedQueue); // [6, 7, 8]
    ((Queue<Integer>) boundedQueue).offer(9);
    System.out.println(boundedQueue); // [7, 8, 9]
    

    【讨论】:

    • 是的。我将使用CircularFifoBuffer 来解决我当前的问题。经过一番搜索(没有太多文档),我将依赖项从commons-collections:commons-collections:3.2 更改为net.sourceforge.collections:collections-generic:4.01。不过,我希望commons-collections 的“官方”通用版能尽快发布。除此之外,如果可以将这样的数据结构添加到 Guava 中,那就太棒了。
    • 类似的功能请求已提交as issue #336。加注星标和/或评论,看看它是否会实施。
    • Guava 刚刚开源了“EvictingQueue”:code.google.com/p/guava-libraries/source/… 好像是我需要的 :)
    • 不错,但要等到 15.0
    【解决方案4】:

    Java's ArrayBlockingQueue 提供固定大小的循环缓冲区。

    【讨论】:

    • enevu 在评论中提到“当数组已满时,数据结构会在数组的开头再次开始写入,覆盖最旧的元素”。这不是ArrayBlockingQueue 所做的。但是,修改它应该不会太难。
    • ArrayBlockingQueue 非常接近我想要的,但我不需要同步。我想我更喜欢 ArrayDeque。但在并发环境中绝对是一个不错的选择。它在满时阻塞的事实并不是什么问题,因为实际上很容易装饰它以在满时删除最旧的元素。
    • 这个答案不正确。添加超出限制的项目时,ArrayBlockingQueue 会阻止(因此得名),而CircularFifoQueue 会丢弃最旧的项目。引用ArrayBlockingQueue docAttempts to put an element into a full queue will result in the operation blocking
    • @BasilBourque,这个答案怎么不正确?问题是“在番石榴中有一个好的替代品吗?”,而不是“是否有一个在各个方面都功能相同的替代品”。此外,OP 直接在您的评论上方声明“它在满时阻塞的事实并不是那么成问题”。发帖前请阅读其他cmets。
    最近更新 更多