【发布时间】:2013-09-03 17:45:42
【问题描述】:
C# 泛型列表System.Collections.Generic.List<T> 是使用可增长数组作为后备存储实现的,其方式类似于更多基于数组列表的实现。很明显,这在执行随机(索引)访问时提供了巨大的好处,例如实现为链表的列表。
我想知道为什么没有选择将其实现为循环数组。对于索引随机访问和附加到列表末尾,这样的实现将具有相同的 O(1) 性能。但会提供额外的好处,例如允许 O(1) 前置操作(即在列表的前面插入新元素)以及平均将随机插入和删除所需的时间减半。
到目前为止的一些答案的总结
正如@Hachet 所指出的,为了使循环数组实现具有与System.Collections.Generic.List<T> 相似的索引性能,它需要始终增长到2 的幂的容量(所以可以执行廉价的模运算)。这意味着不可能像当前可能在构建列表实例时那样将其大小调整为用户提供的确切初始容量。所以这是一个明确的权衡问题。
正如一些快速而肮脏的性能测试所示,对于循环数组实现,索引可能会慢 2-5 倍。
由于索引是一个明显的优先事项,我可以想象与预置操作的更好性能和随机插入/删除的稍微更好的性能相比,这将是太大的惩罚。
这是带有一些补充答案信息的副本
这个问题确实与Why typical Array List implementations aren't double-ended? 有关,我在发布我的问题之前没有发现。我猜它没有以完全令人满意的方式回答:
我没有做过任何基准测试,但在我看来,还有其他瓶颈(例如非本地加载/存储)会大大超过这个。如果我没有听到更有说服力的东西,我可能会接受这个,谢谢。 – Mehrdad 2011 年 5 月 27 日在 4:18
有关此问题的答案提供了有关如何使循环列表的索引表现得相当好的附加信息,包括代码示例和一些使权衡决策更加清晰的量化数字。因此,它们提供的信息与另一个问题中存在的信息相辅相成。所以我同意这个问题的意图是非常相同的,因此我同意它应该被视为重复。但是,如果现在随附此信息的新信息丢失,那将是一种耻辱。
此外,我仍然对可能尚未出现在任何一个问题的答案中的实施选择的潜在其他原因感兴趣。
【问题讨论】:
-
这里有像 Eric Lippert 和 Jon Skeet 这样的人真是太好了。
-
不确定您打算如何同时添加和前置而不需要重新分配列表(即追加-前置-追加序列)?已经有
Queue、LinkeList(可能还有Stack)可能更适合需要访问列表两端的用例。 -
@AlexeiLevenkov 你环绕到分配数组的末尾(即你维护一个“头”和“尾”索引,或“头”和“计数”索引)。追加时减少头部并增加计数,追加时仅增加计数。
标签: c# .net list data-structures arraylist