我在JavaScript中实现了以下链表数据结构:
class Node {
constructor(data, list) {
this.data = data;
this.list = list;
this.prev = null;
this.next = null;
}
remove() {
if (this.prev) {
this.prev.next = this.next;
} else {
this.list.start = this.next;
}
if (this.next) {
this.next.prev = this.prev;
} else {
this.list.end = this.prev;
}
this.next = null;
this.prev = null;
this.list.length -= 1;
}
}
class LinkedList {
constructor() {
this.end = null;
this.start = null;
this.length = 0;
}
append(data) {
const node = new Node(data, this);
if (!this.start) {
this.start = node;
}
if (this.end) {
node.prev = this.end;
this.end.next = node;
}
this.end = node;
this.length += 1;
return data;
}
remove() {
if (this.end) {
return this.end.remove();
}
}
*[Symbol.iterator]() {
let current = this.start;
while (current) {
yield current;
current = current.next;
}
}
}
module.exports = LinkedList;
我像这样使用它来更新动画列表:
static update(timeDelta) {
for (let node of this.animations) {
const animation = node.data;
if (animation.animating) {
animation.update(timeDelta);
} else {
node.remove();
}
}
}
node.remove()
行在我的游戏中造成了非常明显的滞后。我怀疑它正在触发垃圾收集。反常的是,如果我注释掉node.remove()
行,并允许链接列表永远增长,游戏就会顺利运行。
不断添加和删除动画。我在动画update
函数中添加了一些日志:
start iterating linked list
removing
ms elapsed: 0.45499999999992724
end iterating
start iterating linked list
removing
ms elapsed: 0.455000000000382
end iterating
start iterating linked list
removing
ms elapsed: 0.13000000000010914
end iterating
start iterating linked list
(13) updating
ms elapsed: 2.200000000000273
end iterating
您可以看到,链表每秒迭代多次,偶尔会删除一个节点。
如何在不实际导致性能下降的情况下实现从列表中删除O(1(?
node.remove()
线在我的游戏中造成了非常明显的滞后。我怀疑它正在触发垃圾收集。
没有。滞后是因为每个update
调用只更新很少的动画。
您的问题是古老而著名的"迭代期间删除"问题。在您的情况下,它不会触发罕见的边缘情况错误,它只是停止迭代:
while (current) {
yield current;
// after yielding, in the `update` function, we have
// node = current
// and most importantly
// node.remove()
// which assigns (with `this` being `node`)
// this.next = null;
// then the iteration is resumed
current = current.next;
}
哎呀。简单的修复方法是在让步之前缓存下一个要迭代的节点:
let next = this.start;
while (next) {
const current = next;
next = current.next;
yield current;
}
(或类似的东西(,但当然,当删除下一个节点时,它仍然会失败。更好的方法可能是省略行
this.next = null;
this.prev = null;
从节点的remove
方法,以便引用在删除期间保持不变。这不会影响GC。
另一种解决方案是完全删除链接列表-除非您经常在迭代之外的列表中间添加/删除节点,否则它的设计过于复杂。在迭代过程中过滤掉旧动画很简单,它可以用一个好的旧(内存高效?(Array
来完成,即使是在适当的位置:
function filter(array, callback) {
var i=0, j=0;
while (j < array.length) {
if (callback(array[j]))
array[i++] = array[j++];
else
array[i] = array[j++];
}
array.length = i;
}
function update(timeDelta) {
filter(animations, animation => {
var keep = animation.animating;
if (keep) animation.update(timeDelta);
return keep;
});
}
(您可以通过不在i===j
时重新分配来优化filter
(