【问题标题】:Sliding Window: Implementation and Performance (Java)滑动窗口:实现和性能(Java)
【发布时间】:2014-03-28 11:02:05
【问题描述】:

我想实现一个非常简单的滑动窗口。换句话说,我将有某种列表,其中包含从该列表的右端插入并从左端删除的对象。在每次插入中,先前的对象都会左移一个索引。当列表被对象填满时,在从右端每次插入时,都会从左端删除一个对象(之前的对象当然会像往常一样左移一个索引)。

我想到的是 LinkedList 或 ArrayDeque - 可能后者是更好的选择,因为据我所知,在任一端插入和删除都是 ArrayDeque 的持续努力 O(1),即LinkedList 不是这种情况。对吗?

此外,我想问以下问题:当我插入新对象时,左移存储在滑动窗口中的所有先前对象对于像我的情况一样具有 100,000 甚至 1,000,000 个对象的大型滑动窗口来说是处理密集型的.是否有任何其他数据结构可能在我的应用程序中表现更好?

注意:我使用术语“滑动窗口”来表示我想要实现的功能,也许还有其他术语可以更好地描述它,但我认为从上面的描述中我想清楚我想要做什么。

【问题讨论】:

  • 您是否还需要从给定任意索引的容器中快速检索对象的能力? BTW:LinkedList 在 Java 中是一个双链表,这意味着在任一端插入/删除也是 O(1)。
  • @JasonC 不,为此我只需要在一端插入,将先前的对象移动一个索引,然后从另一端删除。我需要快速检索另一个结构,但我解决了这个问题(我想,我现在正在测试它)使用您在上一篇文章中向我建议的“计数器”解决方案的变体。
  • @JasonC 关于 LinkedList,我几天前运行了一个代码,其中一个大小从 100,000 到 1,000,000 不等的 LinkedList 用作滑动窗口。在我看来,这需要很长时间,我认为这可能是由于对象每次移动一个索引。看来我错了!
  • 还有来自Apache Commons CollectionsCircularFifoQueue;该实现类似于ArrayQueue,但它会在添加新元素时自动删除旧元素。

标签: java linked-list garbage-collection sliding-window arraydeque


【解决方案1】:

ArrayDeque 做你想做的事。它不会移动元素。它移动开始和结束位置的索引。添加元素时,结束计数器移动,删除元素时,开始计数器移动。

ArrayDeque 的一个优点是它可以使用更少的内存并且确实会产生垃圾。不利的一面是,它有一个固定的最大尺寸。 LinkedList 增长和缩小。

顺便说一句,如果您想要一个轻量级的滑动窗口或某些值的平均值,指数加权移动平均线要便宜得多,因为您只需要记录两个值,上一次和上一次。

例如

double last = 0;
long lastTime = 0;
double halfLife = 60 * 1000; // 60 seconds for example.

public static double ewma(double sample, long time) {
    double alpha = Math.exp((lastTime - time) / halfLife);
    lastTime = time;
    return last = sample * alpha + last * (1 - alpha); 
}

或者你可以近似这个以避免调用 Math.exp

public static double ewma(double sample, long time) {
    long delay = time - lastTime
    double alpha = delay >= halfLife ? 1.0 : delta / halfLife;
    lastTime = time;
    return last = sample * alpha + last * (1 - alpha); 
}

这要快很多倍,而且在很短的时间间隔内会产生几乎相同的结果。

【讨论】:

  • 因此,如果我理解正确,我有我想要的效果 - 对象的滑动窗口 - 没有将对象移动一个索引的性能损失,因为实际上它们是相应更改的索引被移动的对象(这是等效的),对吗?如果是这样的话...... :)!
  • @PeterHiggs 正确,除了 ArrayQueue 只包含对对象的引用,而不是对象本身。例如。如果从 ArrayList 的开头删除,所有引用都必须向下移动,但对象不会移动。
  • 好的,我会试一试并通知你结果。谢谢!
【解决方案2】:

你说的是Queue吗?看看java.util.LinkedList implementation,因为它实现了Queue 接口。同样LinkedList 的推送和弹出复杂度都是 O(1),但获取的复杂度是 O(N)。

编辑:这是LinkedList的add方法的核心:

Link<ET> next = link.next;
Link<ET> newLink = new Link<ET>(object, link, next);
link.next = newLink;
next.previous = newLink;
link = newLink;
lastLink = null;
pos++;
expectedModCount++;
list.size++;
list.modCount++;

【讨论】:

  • 是的,我认为术语队列甚至更好的 FIFO 描述了我的意思。双向链表(List 接口的 LinkedList 实现实际上在 Java 中)应该可以正常工作。但是,我仍然想知道,因为每次我从 LinkedList 的一端插入一个对象并从另一端删除另一个对象时,LinkedList 中的所有对象都必须移动一个索引,这不是处理要求高吗?肯定不是恒定的 O(1) 努力吗?我的意思是,LinkedList 越大,需要的时间就越多,对吧?
  • 我不想获取,只是推送和弹出(确切地说是添加和删除方法)。我只是想知道 push 是多少 O(1) 因为所有其他对象都必须移动一个索引。
  • 不,LinkedList 的 add 的复杂度是 O(1),这意味着它是恒定的,即不会改变。内部 LinkedList 不在数组中保存东西,而是在一堆相互连接的节点中,所以没有索引。要添加对象,您必须创建一个节点并将其连接到“旧”头节点,不要介意“旧”节点可能导致的节点。
  • 感谢您的回复。我将在我的代码中尝试 LinkedList 和 ArrayDeque,我将检查结果然后提供反馈。代码始终是找出答案的最佳方法,但至少现在我知道我走在正确的轨道上!
猜你喜欢
  • 2015-06-16
  • 2015-02-20
  • 2017-09-04
  • 2021-08-10
  • 2015-07-13
  • 2012-05-09
  • 2022-11-27
  • 2014-07-05
  • 1970-01-01
相关资源
最近更新 更多