List vs --- LinkedList を使用する方がよい場合はいつですか。
この答えに対するコメントを読んでください。人々は私が適切なテストをしなかったと主張します。私はこれが受け入れられた答えであるべきではないことに同意します。私が学んでいたとき、私はいくつかのテストをして、それらを共有したい気がしました。
私は面白い結果を見つけました:
// Temporary class to show the example
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;
}
}
LinkedList<Temp> list = new LinkedList<Temp>();
for (var i = 0; i < 12345678; i++)
{
var a = new Temp(i, i, i, i);
list.AddLast(a);
}
decimal sum = 0;
foreach (var item in list)
sum += item.A;
List<Temp> list = new List<Temp>(); // 2.4 seconds
for (var i = 0; i < 12345678; i++)
{
var a = new Temp(i, i, i, i);
list.Add(a);
}
decimal sum = 0;
foreach (var item in list)
sum += item.A;
基本的にデータにアクセスするだけの場合でも、はるかに遅くなります!!linkedListは絶対に使用しません。
多くの挿入を実行するもう1つの比較です(リストの中央に項目を挿入する予定です)
LinkedList<Temp> list = new LinkedList<Temp>();
for (var i = 0; i < 123456; i++)
{
var a = new Temp(i, i, i, i);
list.AddLast(a);
var curNode = list.First;
for (var k = 0; k < i/2; k++) // In order to insert a node at the middle of the list we need to find it
curNode = curNode.Next;
list.AddAfter(curNode, a); // Insert it after
}
decimal sum = 0;
foreach (var item in list)
sum += item.A;
List<Temp> list = new List<Temp>();
for (var i = 0; i < 123456; i++)
{
var a = new Temp(i, i, i, i);
list.Insert(i / 2, a);
}
decimal sum = 0;
foreach (var item in list)
sum += item.A;
list.AddLast(new Temp(1,1,1,1));
var referenceNode = list.First;
for (var i = 0; i < 123456; i++)
{
var a = new Temp(i, i, i, i);
list.AddLast(a);
list.AddBefore(referenceNode, a);
}
decimal sum = 0;
foreach (var item in list)
sum += item.A;
そのため、複数の項目を挿入する予定があり、かつものどこかに、項目を挿入する予定の場所の参照がある場合に限り、リンクリストを使用します。たくさんのアイテムを挿入する必要があるからといって、挿入したい場所を探すのに時間がかかるため、高速にはなりません。
ほとんどの場合、List<T>
がより便利です。 LinkedList<T>
は、リストの途中で項目を追加/削除するときのコストが低くなりますが、List<T>
は、リストのendで安価に追加/削除できるだけです。
LinkedList<T>
は、シーケンシャルデータ(順方向または逆方向)にアクセスしている場合にのみ最も効率的です。ランダムアクセスは毎回チェーンをたどる必要があるため(比較的インデクサがないため)、比較的高価です。ただし、List<T>
は基本的に単なる(ラッパー付きの)配列なので、ランダムアクセスは問題ありません。
List<T>
はFind
、ToArray
などの多くのサポート方法も提供します。しかし、これらは.NET 3.5/C#3.0のLinkedList<T>
でも拡張メソッドを介して利用できます - それはそれほど要因ではありません。
リンクリストをリストとして考えることは少し誤解を招く可能性があります。鎖のようなものです。実際、.NETでは、LinkedList<T>
はIList<T>
さえ実装していません。リンクリストには実際にはインデックスという概念がありません。クラスで提供されているどのメソッドもインデックスを受け入れません。
リンクリストは、単一リンクでも、二重リンクでもかまいません。これは、チェーン内の各要素が、次の要素へのリンク(1つだけリンクされている)、または前の/次の要素の両方(2重リンク)へのリンクを持っているかどうかを示します。 LinkedList<T>
は二重にリンクされています。
内部的には、List<T>
は配列によって支えられています。これはメモリ内で非常にコンパクトな表現を提供します。逆に、LinkedList<T>
は連続する要素間の双方向リンクを格納するための追加のメモリを必要とします。そのため、LinkedList<T>
のメモリフットプリントは一般にList<T>
よりも大きくなります(List<T>
は、追加操作中のパフォーマンスを向上させるために未使用の内部配列要素を持つことができるという警告があります)。
彼らはまた、異なるパフォーマンス特性を持っています:
LinkedList<T>.AddLast(item)
一定時間List<T>.Add(item)
定率償却、線形最悪の場合LinkedList<T>.AddFirst(item)
一定時間List<T>.Insert(0, item)
線形時間LinkedList<T>.AddBefore(node, item)
一定時間LinkedList<T>.AddAfter(node, item)
一定時間List<T>.Insert(index, item)
線形時間LinkedList<T>.Remove(item)
線形時間LinkedList<T>.Remove(node)
一定時間List<T>.Remove(item)
線形時間List<T>.RemoveAt(index)
線形時間LinkedList<T>.Count
一定時間List<T>.Count
一定時間LinkedList<T>.Contains(item)
線形時間List<T>.Contains(item)
線形時間LinkedList<T>.Clear()
線形時間List<T>.Clear()
線形時間ご覧のとおり、これらはほぼ同等です。実際には、LinkedList<T>
のAPIを使用するのは面倒であり、その内部ニーズの詳細はコードに漏れます。
ただし、リスト内から多数の挿入/削除を行う必要がある場合は、一定の時間がかかります。リスト内の余分な項目は挿入/削除後にシャッフルする必要があるため、List<T>
は線形時間を提供します。
リンクリストは、リストメンバーの非常に速い挿入または削除を提供します。リンクリストの各メンバは、リストの次のメンバへのポインタを含んでいるので、位置iにメンバを挿入します。
リンクリストの短所は、ランダムアクセスが不可能なことです。メンバーにアクセスするには、目的のメンバーが見つかるまでリストをトラバースする必要があります。
ListとLinkedListの違いは、基になる実装にあります。リストは配列ベースのコレクション(ArrayList)です。 LinkedListは、ノードポインタベースのコレクション(LinkedListNode)です。 APIレベルの使用法では、どちらもICollection、IEnumerableなどの同じインターフェイスセットを実装しているため、どちらもほぼ同じです。
パフォーマンスが重要な場合は、主な違いが生じます。たとえば、 "INSERT"操作が多いリストを実装している場合、LinkedListはListよりも優れています。 LinkedListはO(1)時間内にそれを行うことができるので、Listは基礎となる配列のサイズを拡張する必要があるかもしれません。詳細情報や詳細については、LinkedListと配列データ構造のアルゴリズムの違いについてお読みください。 http://ja.wikipedia.org/wiki/Linked_list and Array
この助けを願って、
私の前の答えは十分に正確ではありませんでした。本当に恐ろしいことでした。Dしかし、今ではもっと有用で正しい答えを投稿することができます。
私はいくつか追加のテストをしました。以下のリンクからソースを見つけて、あなた自身の環境でそれを再チェックすることができます。 https://github.com/ukushu/DataStructuresTestsAndOther.git
簡単な結果:
配列は使用する必要があります:
リストは使用する必要があります:
LinkedListは使用する必要があります:
詳細:
内部的にはLinkedList<T>
は.NETのリストではありません。 IList<T>
も実装されていません。そしてそれが、欠けているインデックスとインデックスに関連するメソッドがある理由です。
LinkedList<T>
はノードポインタベースのコレクションです。 .NETでは、二重リンクの実装になっています。これは、前/次の要素が現在の要素にリンクしていることを意味します。そしてデータは断片化されています - 異なるリストオブジェクトはRAMの異なる場所に配置することができます。また、LinkedList<T>
またはArrayよりもList<T>
に使用されるメモリが多くなります。
.NETのList<T>
は、Javaの代わりのArrayList<T>
です。これはこれが配列ラッパーであることを意味します。そのため、連続した1つのデータブロックとしてメモリに割り当てられます。割り当てられたデータサイズが85000バイトを超えると、ラージオブジェクトヒープに移動されます。サイズによっては、これがヒープの断片化を引き起こす可能性があります(軽度の形式のメモリリーク)。しかし、同時にサイズ<85000バイトの場合 - これはメモリ内で非常にコンパクトで高速アクセスの表現を提供します。
ランダムアクセスのパフォーマンスとメモリ消費のためには単一の連続ブロックが好まれますが、サイズを規則的に変更する必要があるコレクションの場合は、通常Arrayなどの構造を新しい場所にコピーする必要があります。ノードを削除しました。
配列に対するリンクリストの主な利点は、リンクによってアイテムを効率的に並べ替えることができることです。 Sedgewick、p。 91
LinkedListを使用する一般的な状況は次のとおりです。
例えば100,000と言う大きなサイズの文字列のリストから多くの特定の文字列を削除したいとします。削除する文字列はHashSetのdicで調べることができ、文字列のリストには削除する文字列が30,000から60,000の間に含まれると考えられています。
それでは、10万個の文字列を格納するのに最適な種類のリストは何ですか?答えはLinkedListです。それらがArrayListに格納されている場合、それを反復処理し、一致したStringsを削除すると最大で数十億の操作が必要になりますが、イテレータとremove()メソッドを使用すると約100,000操作になります。
LinkedList<String> strings = readStrings();
HashSet<String> dic = readDic();
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()){
String string = iterator.next();
if (dic.contains(string))
iterator.remove();
}
組み込みのインデックス付きアクセス、ソート(およびこのバイナリ検索の後)、および "ToArray()"メソッドが必要な場合は、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)ランダムアクセスのようにパフォーマンスが良くなる他の分野があります。
基本的に、.NETのList<>
は配列のラッパーです。 LinkedList<>
リンクリスト。問題は配列とリンクリストの違い、そしてリンクリストの代わりに配列を使うべきなのはいつかということです。どちらを使用するかの決定において、おそらく最も重要な2つの要因は次のようになります。
LinkedList<>
を使用する場合
Token Stream
。それ以外の場合は、List<>
を使用することをお勧めします。
私は上記の点の大部分に同意します。そして私は、Listがほとんどの場合、もっと明白な選択のように見えることにも同意します。
しかし、LinkedListがListよりもはるかに優れた効率のための選択である多くの例があることを付け加えたいと思います。
誰かがこれらのコメントが役に立つことを願ってください。