1 个不稳定版本
使用旧的 Rust 2015
0.0.1 | 2015年4月10日 |
---|
#26 在 #strategies
1KB
不打算维护
此存储库存在是为了发布我在某个周末进行的一些实验的结果。这不是一个随着时间的推移而被维护的软件项目。
如果您想使用此代码,请考虑将其复制到自己的源存储库中,并遵守MIT许可条款。
rust-forest
Rust 中“DOM-like”树数据结构的各种实现策略。
在这里,“DOM-like”意味着可以使用数据结构来表示HTML或XML文档的解析内容,就像DOM所做的那样,但不必具有与DOM完全相同的API。也就是说
- 树由节点组成。
- 每个节点可以有零个或多个 子节点,这些节点是有序的。
- 每个节点最多有一个 父节点,即它是其 子节点。
- 没有 父节点 的节点被称为 根节点。
- 因此,每个节点也可能有 兄弟节点:如果有的话,它的 父节点 的其他 子节点。
- 从任何给定的节点,访问其父节点、前一个兄弟节点、后一个兄弟节点、第一个子节点和最后一个子节点(如果有),最多需要 O(1) 时间。
- 每个节点还关联着一些数据,对于本项目来说,这些数据是纯泛型的。对于HTML文档,数据可以是文本节点的文本,或者是元素节点的名称和属性。
- 树是可变的:节点(及其子树)可以在树的任何位置插入或删除。
特别是,需要访问父节点意味着我们不能使用明显的结构,即子节点被父节点直接 拥有。
struct Node<T> {
data: T,
children: Vec<Node<T>>,
parent: /* ??? */
}
更普遍地,具有父子关系和兄弟关系(除子关系外)的树可以被视为一种特殊类型的图,但仍然是一个包含环的图。而Rust的默认所有权模型并不容易支持环。因此,我们需要更复杂的策略来管理节点的生命周期。
在 Servo 中,DOM节点由JavaScript垃圾回收器管理。然而,Rust目前还没有自己的跟踪垃圾回收器,并且许多Rust项目可能希望操作DOM-like树而不嵌入整个JavaScript虚拟机。
此存储库包含多个crate,每个crate都使用不同的方法实现不同的权衡来实现DOM-like数据结构。
rctree
节点的生命周期通过 引用计数 来管理。为了避免导致内存泄漏的引用循环,树是 不对称的:每个节点持有对下一个兄弟节点和第一个子节点的可选 强引用,但只对父节点、前一个兄弟节点和最后一个子节点持有可选的 弱引用。
节点一旦没有强引用,就会被销毁。结构是这样的,只要持有根节点的引用,就可以保持整棵树的生命。然而,如果从树外部的唯一引用是用来遍历树的,那么在向下遍历之后,将无法回到树上的祖先和之前的兄弟节点,因为这些节点已经被销毁了。
对已销毁节点的弱引用被视为未设置。例如,当其父节点被销毁时,节点可以成为根节点。
由于节点被别名(存在多个引用),所以使用RefCell
来实现内部可变。
优势
- 有一个可见的
NodeRef
类型来操作树,并具有相应的方法 - 内存一旦不再使用就会被释放(如果删除树的某些部分)。
劣势
- 树只能从创建它的线程中访问。
- 任何树操作,包括只读遍历,都需要增加和减少引用计数,这会导致运行时开销
- 节点单独分配,这可能会导致内存碎片化并影响性能。
arena-tree
节点生命周期通过场分配器进行管理。
节点通过&'a T
引用与场的生命周期相关联,当场被销毁时,节点将同时被销毁。节点之间的链接在内部也是&'a T
引用。
由于节点被别名(存在多个引用),所以使用Cell
来实现内部可变。
优势
- 节点仍然有方法,在树操作过程中可以大致忽略场(只要它保持存活)。
- 运行时开销更小。(分配速度快,不需要增加或减少引用计数。)
- 节点在内存中分配成大块连续的内存。这有助于提高内存缓存性能。
劣势
- 还有一个对象(场)需要保留。
- 树只能从创建它的线程中访问。
- 当节点在场被销毁之前从树中移除时,会发生内存“泄漏”。可以通过维护一个空闲列表来缓解这个问题,该列表包含不再使用的节点,并且可以被重用,但前提是这些节点已经被显式释放。(而引用计数和垃圾回收会自动释放未使用的节点。)但这样并不能阻止保留对已释放节点的引用。可以添加另一个显式机制(例如在每个节点中跟踪一个生成号),以防止(主要是)此类引用和访问已释放或重新分配的节点。
idtree
与arena-tree
类似,但场被简化为单个Vec
,并使用数值标识符(向量中的索引)而不是&'a T
引用。
优势
- 没有
RefCell
,可变性的处理方式更符合Rust的惯例,即通过唯一的(&mut
)访问场。 - 树可以像
Vec
一样发送或跨线程共享。这允许进行并行树遍历,例如。
劣势
- 每次访问都需要通过场进行,这可能很麻烦。
- 由于边界检查,与
arena-tree
相比,存在一些运行时开销。 - 来自给定场的节点ID可以在不同的场中使用,这不太可能导致错误并访问无关的节点。