我终于开始学习链接列表了。据我所知,在垃圾收集环境中,当取消引用循环链表头时,整个列表最终都是GC。
我对Objective-C和Swift有很多经验,它们都使用自动引用计数而不是GC。据我了解,这将创建一个强大的参考周期(as explained by Apple's ARC docs),因为头节点将保持对后面的强引用,后者也保持对头部的强引用。我偶然做到了这一点,我遇到了很强的保留周期。
在我看来,这会产生一个主要问题,因为它使循环链表完全无法释放,因为在ARC下,至少在Objective C和Swift中,你无法手动释放对象。
这里的解决方案是在每个节点上为“前”和“后”引用保留一个额外的弱引用属性,因为它们不能使用公共next
引用(因为它们需要很强或者否则列表中间的其他节点将被解除分配)?或者,在删除对头部的最终引用之前,我应该通过破坏列表中的所有引用来伪手动取消分配我的列表吗?所有这些看起来都不像解决方案那么漂亮和黑客。
答案 0 :(得分:1)
实际上,拥有一个循环链表,你最后一个节点强引用第一个节点将导致第一个节点始终至少有一个引用,因此列表将始终保留在内存中。
虽然ARC Wikipedia article表明,有时这样的周期可以被忽略,在这种情况下它是不可接受的,因为作为数据结构设计者,我们不控制链表的大小 - 它可以而且应该能够根据定义无休止地成长。
我的解决方案是让节点知道他们的容器(即弱引用列表本身)。这样我们就可以通过使next
成为节点的计算属性来删除循环引用。设置实际的下一个节点后,我们将其返回为next
。当它是nil
时,我们会返回container
' startNode
,因此我们正在使用API,它将自己显示为循环链接列表,但是它只是在它的引导下通常的。
以下是一些代码示例:
class CircularLinkedList<T> {
// MARK: - Internal structures
class Node {
let value: T
private var _next: Node?
weak fileprivate var container: CircularLinkedList<T>!
var next: Node! {
get {
if let next = _next {
return next
} else {
return container.startNode
}
}
set {
_next = newValue
}
}
init(value: T) {
self.value = value
}
}
// MARK: - Properties
var startNode: Node
var endNode: Node
// MARK: - Constructors
init(initialValue: T) {
startNode = Node(value: initialValue)
endNode = startNode
startNode.container = self
}
// MARK: - API
func append(newValue: T) {
let newNode = Node(value: newValue)
newNode.container = self
endNode.next = newNode
endNode = newNode
}
}
有人可能会说,让Node
了解其容器列表并不是一个好主意的架构。但是让它fileprivate
我们将它从外部世界隐藏起来,而在数据结构中我们知道我们应该正确地使用它。这似乎是比在列表的生命周期内手动打破参考周期更好的解决方案。