什么时候使用List和LinkedList更好?
当前回答
将链表视为列表可能会有一些误导。它更像一个链条。事实上,在。net中,LinkedList<T>甚至没有实现IList<T>。在链表中没有真正的索引概念,即使看起来有。当然,类中提供的方法都不接受索引。
链表可以是单链,也可以是双链。这是指链中的每个元素是否只与下一个元素有链接(单链),还是与前一个/下一个元素都有链接(双链)。LinkedList<T>是双重链接。
在内部,List<T>由一个数组支持。这在内存中提供了一个非常紧凑的表示。相反,LinkedList<T>涉及额外的内存来存储连续元素之间的双向链接。因此LinkedList<T>的内存占用通常会比List<T>的内存占用大(需要注意的是,List<T>可以有未使用的内部数组元素,以提高追加操作期间的性能)。
它们也有不同的表现特征:
附加
LinkedList<T>.AddLast(item)常量时间 List<T>.Add(item)平摊常数时间,线性最坏情况
预谋
LinkedList<T>.AddFirst(item)常量时间 列表> < T。插入(0,项)线性时间
插入
LinkedList < T >。AddBefore(节点,项目)常量时间 LinkedList < T >。AddAfter(节点,项目)常量时间 列表> < T。插入(索引、项)线性时间
删除
删除(项目)线性时间 删除(节点)常量时间 列表<T>.删除(项目)线性时间 List<T>.RemoveAt(index)线性时间
数
LinkedList < T >。计算常数时间 列表> < T。计算常数时间
包含
包含(项目)线性时间 列表<T>.包含(项)线性时间
清晰的
LinkedList<T>.Clear()线性时间 List<T>.Clear()线性时间
如你所见,它们基本上是相等的。实际上,LinkedList<T>的API使用起来更麻烦,其内部需求的细节会泄漏到代码中。
然而,如果你需要在一个列表中做很多插入/删除,它提供了常数时间。List<T>提供线性时间,因为列表中的额外项必须在插入/删除之后重新排列。
其他回答
我问了一个类似的关于LinkedList集合性能的问题,发现Steven Cleary的Deque c#实现是一个解决方案。与Queue集合不同,Deque允许前后移动项目。它类似于链表,但性能有所改进。
使用LinkedList<>时
你不知道有多少东西会通过防洪闸门。例如,令牌流。 当你只想在结尾删除\插入。
对于其他内容,最好使用List<>。
这是改编自Tono Nam的公认的答案,纠正了一些错误的测量。
测试:
static void Main()
{
LinkedListPerformance.AddFirst_List(); // 12028 ms
LinkedListPerformance.AddFirst_LinkedList(); // 33 ms
LinkedListPerformance.AddLast_List(); // 33 ms
LinkedListPerformance.AddLast_LinkedList(); // 32 ms
LinkedListPerformance.Enumerate_List(); // 1.08 ms
LinkedListPerformance.Enumerate_LinkedList(); // 3.4 ms
//I tried below as fun exercise - not very meaningful, see code
//sort of equivalent to insertion when having the reference to middle node
LinkedListPerformance.AddMiddle_List(); // 5724 ms
LinkedListPerformance.AddMiddle_LinkedList1(); // 36 ms
LinkedListPerformance.AddMiddle_LinkedList2(); // 32 ms
LinkedListPerformance.AddMiddle_LinkedList3(); // 454 ms
Environment.Exit(-1);
}
代码是:
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace stackoverflow
{
static class LinkedListPerformance
{
class Temp
{
public decimal A, B, C, D;
public Temp(decimal a, decimal b, decimal c, decimal d)
{
A = a; B = b; C = c; D = d;
}
}
static readonly int start = 0;
static readonly int end = 123456;
static readonly IEnumerable<Temp> query = Enumerable.Range(start, end - start).Select(temp);
static Temp temp(int i)
{
return new Temp(i, i, i, i);
}
static void StopAndPrint(this Stopwatch watch)
{
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);
}
public static void AddFirst_List()
{
var list = new List<Temp>();
var watch = Stopwatch.StartNew();
for (var i = start; i < end; i++)
list.Insert(0, temp(i));
watch.StopAndPrint();
}
public static void AddFirst_LinkedList()
{
var list = new LinkedList<Temp>();
var watch = Stopwatch.StartNew();
for (int i = start; i < end; i++)
list.AddFirst(temp(i));
watch.StopAndPrint();
}
public static void AddLast_List()
{
var list = new List<Temp>();
var watch = Stopwatch.StartNew();
for (var i = start; i < end; i++)
list.Add(temp(i));
watch.StopAndPrint();
}
public static void AddLast_LinkedList()
{
var list = new LinkedList<Temp>();
var watch = Stopwatch.StartNew();
for (int i = start; i < end; i++)
list.AddLast(temp(i));
watch.StopAndPrint();
}
public static void Enumerate_List()
{
var list = new List<Temp>(query);
var watch = Stopwatch.StartNew();
foreach (var item in list)
{
}
watch.StopAndPrint();
}
public static void Enumerate_LinkedList()
{
var list = new LinkedList<Temp>(query);
var watch = Stopwatch.StartNew();
foreach (var item in list)
{
}
watch.StopAndPrint();
}
//for the fun of it, I tried to time inserting to the middle of
//linked list - this is by no means a realistic scenario! or may be
//these make sense if you assume you have the reference to middle node
//insertion to the middle of list
public static void AddMiddle_List()
{
var list = new List<Temp>();
var watch = Stopwatch.StartNew();
for (var i = start; i < end; i++)
list.Insert(list.Count / 2, temp(i));
watch.StopAndPrint();
}
//insertion in linked list in such a fashion that
//it has the same effect as inserting into the middle of list
public static void AddMiddle_LinkedList1()
{
var list = new LinkedList<Temp>();
var watch = Stopwatch.StartNew();
LinkedListNode<Temp> evenNode = null, oddNode = null;
for (int i = start; i < end; i++)
{
if (list.Count == 0)
oddNode = evenNode = list.AddLast(temp(i));
else
if (list.Count % 2 == 1)
oddNode = list.AddBefore(evenNode, temp(i));
else
evenNode = list.AddAfter(oddNode, temp(i));
}
watch.StopAndPrint();
}
//another hacky way
public static void AddMiddle_LinkedList2()
{
var list = new LinkedList<Temp>();
var watch = Stopwatch.StartNew();
for (var i = start + 1; i < end; i += 2)
list.AddLast(temp(i));
for (int i = end - 2; i >= 0; i -= 2)
list.AddLast(temp(i));
watch.StopAndPrint();
}
//OP's original more sensible approach, but I tried to filter out
//the intermediate iteration cost in finding the middle node.
public static void AddMiddle_LinkedList3()
{
var list = new LinkedList<Temp>();
var watch = Stopwatch.StartNew();
for (var i = start; i < end; i++)
{
if (list.Count == 0)
list.AddLast(temp(i));
else
{
watch.Stop();
var curNode = list.First;
for (var j = 0; j < list.Count / 2; j++)
curNode = curNode.Next;
watch.Start();
list.AddBefore(curNode, temp(i));
}
}
watch.StopAndPrint();
}
}
}
你可以看到结果与其他人在这里记录的理论性能是一致的。很清楚- LinkedList<T>在插入的情况下获得了很大的时间。我还没有测试从列表中间删除,但结果应该是相同的。当然,List<T>在其他方面表现得更好,比如O(1)随机访问。
当您需要内置索引访问、排序(以及二进制搜索之后)和“ToArray()”方法时,您应该使用List。
本质上,. net中的List<>是数组的包装器。LinkedList<>是一个链表。所以问题归结为,数组和链表之间的区别是什么,以及什么时候应该使用数组而不是链表。可能在你决定使用哪个时最重要的两个因素可以归结为:
Linked lists have much better insertion/removal performance, so long as the insertions/removals are not on the last element in the collection. This is because an array must shift all remaining elements that come after the insertion/removal point. If the insertion/removal is at the tail end of the list however, this shift is not needed (although the array may need to be resized, if its capacity is exceeded). Arrays have much better accessing capabilities. Arrays can be indexed into directly (in constant time). Linked lists must be traversed (linear time).
推荐文章
- 实体框架核心:在上一个操作完成之前,在此上下文中开始的第二个操作
- 如何为构造函数定制Visual Studio的私有字段生成快捷方式?
- 为什么Visual Studio 2015/2017/2019测试运行器没有发现我的xUnit v2测试
- 如何使用JSON确保字符串是有效的JSON。网
- AppSettings从.config文件中获取值
- 通过HttpClient向REST API发布一个空体
- 如何检查IEnumerable是否为空或空?
- 自动化invokerrequired代码模式
- 没有ListBox。SelectionMode="None",是否有其他方法禁用列表框中的选择?
- 在c#代码中设置WPF文本框的背景颜色
- 在c#中,什么是单子?
- c#和Java中的泛型有什么不同?和模板在c++ ?
- c#线程安全快速(est)计数器
- HyperLogLog算法是如何工作的?
- 如何将此foreach代码转换为Parallel.ForEach?