#参考 #列表 #元素 #保留 #只追加 #结构 #推入

appendlist

一个只追加列表,保留对其元素的引用

6个版本 (稳定)

1.4.0 2019年7月6日
1.3.0 2019年7月6日
1.1.0 2019年6月29日
0.1.0 2019年6月23日

数据结构 中排名第 1417

Download history 1745/week @ 2024-03-08 1702/week @ 2024-03-15 2326/week @ 2024-03-22 2316/week @ 2024-03-29 1757/week @ 2024-04-05 1669/week @ 2024-04-12 2338/week @ 2024-04-19 1680/week @ 2024-04-26 1311/week @ 2024-05-03 1307/week @ 2024-05-10 1116/week @ 2024-05-17 1041/week @ 2024-05-24 1234/week @ 2024-05-31 959/week @ 2024-06-07 1112/week @ 2024-06-14 949/week @ 2024-06-21

每月下载量 4,389
6 颗粒中使用(5个直接使用)

MIT 许可证

23KB
261

AppendList:一个只追加列表,保留对其元素的引用

Build Status

此列表允许您在持有对列表中某个元素的引用时添加新元素到末尾。它还避免了重新分配。

何时使用它?

  • 您需要在列表的其他元素被借用时插入
  • 您有如此多的数据,以至于 Vec 的重新分配非常显著

不应何时使用它?

  • 您存储的是列表中的索引,而不是实际的引用(只需使用 Vec<T> 即可)
  • Vec 的重新分配并不重要(通常是这种情况!)

有哪些功能?

  • 一个 push(&self, item: T) 方法(您会期望 push(&mut self, item: T)
  • 非摊销常数时间插入和索引(通常插入是 摊销 常数时间,如 Vec,或者索引是线性时间,如 LinkedList
  • 您可以在列表中保留引用
  • 只有2行不安全的代码

这让我能做什么?

以下示例无法编译,因为当调用 list.push() 时,second_element 有对 list 的引用。

let list: Vec<u32> = (1..=10).collect();

let second_element = &list[1];

list.push(11); // Push needs &mut self, but list is already borrowed

assert_eq!(*second_element, 2); // Fails to compile

但如果你只是用 AppendList 替换 Vec,一切都会正常工作!

use appendlist::AppendList;

let list: AppendList<u32> = (1..=10).collect();

let second_element = &list[1];

list.push(11); // Push only needs &self, so this works fine

assert_eq!(*second_element, 2); // All OK!

所有这些关于重新分配的是什么意思?

一般来说,Vec 非常酷,你应该默认使用它。但是它有一个弱点:当你创建它时,它会有一个有限的空间。当空间用完时,它需要重新分配:获取一块新的内存(通常是当前大小的两倍),然后复制所有内容,然后释放旧内存。

重新分配需要 O(n) 的时间:你需要复制列表中的所有 n 个元素。但是你不必经常这样做:只需要 O(log n) 次重新分配就可以进行 n 次插入(对于当前的 Vec 实现)。实际上,重新分配很少,如果你将它们分散到所有的插入中,它们只是增加了常数额外时间。这就是为什么 Rust 文档说 Vec 有 "O(1) 摊销 插入" —— 它通常需要常数时间来插入,偶尔需要线性时间,但如果将那些昂贵的插入分散到所有的便宜插入中,它仍然是常数。

重新分配还有一个问题:对 Vec 内部元素的任何引用都会失效。如果你在元素被复制时有一个对它的引用,你的引用将无法知道其新位置,仍然会指向旧位置,现在是无效内存。使用那个引用将是一个 use-after-free 漏洞,因此 Rust 强制你在推送另一个元素之前没有对 Vec 的引用,以防那个推送会重新分配。

AppendList 通过保留数据块来解决这个问题。当你推送一个新元素时,它会被添加到当前块的末尾。如果一个块满了,而不是重新分配它,AppendList 创建一个全新的空块。每个块的大小是上一个块的两倍,所以只需要 O(log n) 次分配,并且每次分配的时间是常数(对于大多数分配器)而不是线性时间。通过消除重新分配,AppendList 相比于 Vec 提供了一些好处。

  • 你可以保持引用在推送时
  • 你不必支付偶尔的线性重新分配和复制成本

然而,它也有一些缺点

  • 索引需要更长的时间(你必须先索引到块中,然后索引到你的项目)
  • CPU 缓存行为可能在块边界附近稍微差一些(因为下一个块通常不是连续的)

我应该使用这个 crate 吗?

可能不是。

一般来说,你应该只使用一个 Vec 并跟踪索引而不是引用。但如果保持引用非常重要,那么这是你的解决方案。

没有运行时依赖