Ne zaman bir kullanmak daha iyidir Listesini bir vs LinkedList ?
Ne zaman bir kullanmak daha iyidir Listesini bir vs LinkedList ?
Yanıtlar:
Lütfen bu cevabın yorumlarını okuyun. İnsanlar uygun testler yapmadığımı iddia ediyorlar. Bunun kabul edilmiş bir cevap olmaması gerektiğine katılıyorum. Öğrenirken bazı testler yaptım ve bunları paylaşmak gibi hissettim.
İlginç sonuçlar buldum:
// 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;
Sadece verilere erişseniz bile çok daha yavaştır !! Asla LinkedList kullanma diyorum.
Çok sayıda ekleme yapan başka bir karşılaştırma (listenin ortasına bir öğe eklemeyi planlıyoruz)
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;
Bu nedenle, yalnızca birkaç öğe eklemeyi planlıyorsanız ve ayrıca bir yere öğeyi nereye eklemeyi planladığınıza ilişkin referansınız varsa bağlantılı bir liste kullanın. Çok fazla öğe eklemeniz gerektiğinden, daha hızlı yapmaz çünkü eklemek istediğiniz konumu aramak zaman alır.
list.AddLast(a);
son iki LinkedList örneğindeki döngü içinde? Bunu, bir önceki list.AddLast(new Temp(1,1,1,1));
LinkedList'in yanında olduğu gibi, döngüden önce bir kez yapıyorum , ancak döngülere iki kat daha fazla Temp nesnesi eklediğiniz gibi görünüyor. (Ve kendimi bir test uygulamasıyla tekrar kontrol ettiğimde , yeterince, LinkedList'te iki kat daha fazla.)
I say never use a linkedList.
daha sonraki yayınınızda görüldüğü gibi genel tavsiyeniz hatalı. Düzenlemek isteyebilirsiniz. 2) Zamanlama nedir? Tek bir adımda örnekleme, toplama ve numaralandırma? Çoğunlukla, örnekleme ve numaralandırma, ppl'nin endişe duyduğu şey değildir, bunlar bir defalık adımlardır. Eklerin ve eklemelerin özellikle zamanlanması daha iyi bir fikir verecektir. 3) En önemlisi, bağlantılı bir listeye gerekenden fazlasını ekliyorsunuz. Bu yanlış bir karşılaştırma. Bağlantılı liste hakkında yanlış fikir yayar.
Çoğu durumda, List<T>
daha yararlıdır. LinkedList<T>
listenin ortasındaki öğeleri eklerken / kaldırırken daha az maliyete sahip olurken , listenin sonundaList<T>
yalnızca ucuza ekleyebilir / kaldırabilirsiniz .
LinkedList<T>
yalnızca sıralı verilere (ileri veya geri) erişiyorsanız en etkilidir - rastgele erişim nispeten pahalıdır, çünkü her seferinde zincirde yürümelidir (bu yüzden neden bir indeksleyiciye sahip değildir). Ancak, a List<T>
aslında sadece bir dizi (bir sarıcı ile) rastgele erişim iyidir.
List<T>
Ayrıca destek yöntemlerinin çok - biraz Find
, ToArray
vb; ancak, bunlar LinkedList<T>
.NET 3.5 / C # 3.0 ile uzantı yöntemleri aracılığıyla da kullanılabilir - bu nedenle daha az faktördür.
List<T>
ve T[]
çok tıknaz olduğu için başarısız olacak (hepsi bir levha), LinkedList<T>
çok parçalı olmasını bekleyecek (eleman başına levha).
Bağlantılı bir listeyi liste olarak düşünmek biraz yanıltıcı olabilir. Daha çok bir zincir gibi. Aslında, .NET'te LinkedList<T>
bile uygulamaz IList<T>
. Bağlantılı bir listede gerçek bir dizin kavramı yoktur, ancak görünse de. Sınıfta sağlanan yöntemlerin hiçbiri dizinleri kabul etmez.
Bağlantılı listeler tek başlarına veya iki kez bağlanabilir. Bu, zincirdeki her bir elemanın sadece bir sonrakine (tek başına bağlı) veya hem önceki / sonraki elemanlara (iki kez bağlı) bir bağlantıya sahip olup olmadığını ifade eder. LinkedList<T>
iki kez bağlantılıdır.
Dahili olarak List<T>
bir dizi tarafından desteklenir. Bu, bellekte çok kompakt bir gösterim sağlar. Tersine, LinkedList<T>
birbirini izleyen elemanlar arasındaki çift yönlü bağlantıları saklamak için ek bellek içerir. Bu nedenle, a'nın bellek alanı LinkedList<T>
genellikle olduğundan daha büyük olacaktır List<T>
( List<T>
ekleme işlemleri sırasında performansı artırmak için kullanılmayan dahili dizi öğelerine sahip olabilecek uyarı ile ).
Farklı performans özellikleri de vardır:
LinkedList<T>.AddLast(item)
sabit zamanList<T>.Add(item)
itfa edilmiş sabit zamanlı, doğrusal en kötü durumLinkedList<T>.AddFirst(item)
sabit zamanList<T>.Insert(0, item)
doğrusal zamanLinkedList<T>.AddBefore(node, item)
sabit zamanLinkedList<T>.AddAfter(node, item)
sabit zamanList<T>.Insert(index, item)
doğrusal zamanLinkedList<T>.Remove(item)
doğrusal zamanLinkedList<T>.Remove(node)
sabit zamanList<T>.Remove(item)
doğrusal zamanList<T>.RemoveAt(index)
doğrusal zamanLinkedList<T>.Count
sabit zamanList<T>.Count
sabit zamanLinkedList<T>.Contains(item)
doğrusal zamanList<T>.Contains(item)
doğrusal zamanLinkedList<T>.Clear()
doğrusal zamanList<T>.Clear()
doğrusal zamanGördüğünüz gibi, çoğunlukla eşdeğerler. Uygulamada, API'sini LinkedList<T>
kullanmak daha zahmetlidir ve dahili ihtiyaçlarının ayrıntıları kodunuza dökülür.
Ancak, bir listeden çok sayıda ekleme / kaldırma işlemi yapmanız gerekiyorsa, sabit bir süre sunar. List<T>
ekleme / çıkarma işleminden sonra listedeki fazladan öğelerin karıştırılması gerektiği için doğrusal zaman sunar.
Bağlantılı listeler, liste üyesinin çok hızlı eklenmesi veya silinmesini sağlar. Bağlantılı listedeki her üye, i konumunda bir üye eklemek için listedeki bir sonraki üyeye bir işaretçi içerir:
Bağlantılı bir listenin dezavantajı, rasgele erişimin mümkün olmamasıdır. Bir üyeye erişmek için, istenen üye bulunana kadar listeyi dolaşmak gerekir.
Önceki cevabım yeterince doğru değildi. Gerçekten korkunçtu: D Ama şimdi çok daha yararlı ve doğru cevap gönderebilirim.
Bazı testler yaptım. Kaynağını aşağıdaki bağlantıdan bulabilir ve kendi ortamınızda yeniden kontrol edebilirsiniz: https://github.com/ukushu/DataStructuresTestsAndOther.git
Kısa sonuçlar:
Dizi kullanmanız gerekir:
Liste kullanmanız gerekir:
LinkedList şunları kullanmalıdır:
Daha fazla detay:
LinkedList<T>
dahili olarak .NET'te bir Liste değildir. Uygulanmıyor bile IList<T>
. İşte bu yüzden endekslerle ilgili olmayan indeksler ve yöntemler var.
LinkedList<T>
düğüm-işaretçi tabanlı bir koleksiyon. .NET'te çift bağlantılı uygulamada. Bu, önceki / sonraki öğelerin geçerli öğeye bağlantısı olduğu anlamına gelir. Ve veriler parçalanır - farklı liste nesneleri farklı RAM yerlerine yerleştirilebilir. Ayrıca LinkedList<T>
, için List<T>
veya Array'dan daha fazla bellek kullanılacak .
List<T>
Net, Java'nın alternatifidir ArrayList<T>
. Bu, bunun dizi sarıcı olduğu anlamına gelir. Bu yüzden bellekte bitişik bir veri bloğu olarak ayrılır. Tahsis edilen veri boyutu 85000 baytı aşarsa, Büyük Nesne Yığını'na taşınır. Boyutuna bağlı olarak, bu yığın parçalanmasına (hafif bir bellek sızıntısı formu) yol açabilir. Ancak aynı zamanda <85000 bayt boyutundaysa - bu, bellekte çok kompakt ve hızlı erişim temsili sağlar.
Rastgele erişim performansı ve bellek tüketimi için tek bitişik blok tercih edilir, ancak boyutu düzenli olarak değiştirmesi gereken koleksiyonlar için Array gibi bir yapının genellikle yeni bir konuma kopyalanması gerekir, oysa bağlantılı bir listenin yalnızca yeni eklenenler için belleği yönetmesi gerekir / silinmiş düğümler.
List ve LinkedList arasındaki fark temel uygulamalarında yatmaktadır. Liste, dizi tabanlı koleksiyondur (ArrayList). LinkedList, düğüm işaretçisi tabanlı bir koleksiyon (LinkedListNode). API düzeyinde kullanımda, ikisi de hemen hemen aynıdır, çünkü her ikisi de ICollection, IEnumerable gibi aynı arabirim kümesini uygular.
Temel fark performans önemli olduğunda ortaya çıkar. Örneğin, ağır "INSERT" işlemi olan listeyi uyguluyorsanız, LinkedList List'ten daha iyi performans gösterir. LinkedList bunu O (1) sürede yapabildiğinden, List'in temeldeki dizinin boyutunu genişletmesi gerekebilir. Daha fazla bilgi / ayrıntı için LinkedList ve dizi veri yapıları arasındaki algoritmik farkı okumak isteyebilirsiniz.http://en.wikipedia.org/wiki/Linked_list ve Dizi
Umarım bu yardım,
Add
her zaman mevcut dizinin sonunda olan durumu düşünüyorsunuz . List
O (1) olmasa bile "yeterince iyi" dir. Sonunda olmayan birçok Add
s gerekiyorsa ciddi sorun oluşur . Marc, her eklediğinizde (yalnızca yeniden boyutlandırma gerektiğinde değil) mevcut verilerin taşınması gerektiğinin daha önemli bir performans maliyeti olduğuna dikkat çekiyor . List
Bağlantılı listelerin dizilere göre birincil avantajı, bağlantıların öğeleri verimli bir şekilde yeniden düzenleme olanağı sağlamasıdır. Sedgewick, s. 91
LinkedList'i kullanmak için yaygın bir durum şöyledir:
Diyelim ki 100.000 gibi büyük boyutlu dizelerin listesinden birçok dizeyi kaldırmak istediğinizi varsayalım. Kaldırılacak dizeler HashSet dic'de aranabilir ve dizeler listesinin kaldırılacak 30.000 ila 60.000 arasında dizeyi içerdiğine inanılır.
Peki 100.000 Dizeyi saklamak için en iyi Liste türü nedir? Cevap LinkedList. Bir ArrayList içinde saklanırlarsa, o zaman tekrarlar ve milyarlarca işlem alabilen eşleşen Dizeleri kaldırırlar, ancak bir yineleyici ve remove () yöntemini kullanarak yaklaşık 100.000 işlem alır.
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();
}
RemoveAll
bir öğe kaldırma List
etrafında öğeleri bir sürü hareket etmeden veya kullanmak Where
ikinci bir liste oluşturmak için LINQ dan. Bir kullanılması LinkedList
ancak burada tüketen biter dramatik biraz daha kötü bir daha yapma, bu önemli ölçüde daha yavaş yinelemenizi olacağı diğer koleksiyonların türleri ve hafıza mevkiinde araçlarının kaybı daha fazla bellek List
.
RemoveAll
Java'da bir eşdeğeri olup olmadığından emin değilim .
RemoveAll
, List
Tom'un döngüsüne benzeyen bir "sıkıştırma" algoritması yapabilirsiniz, ancak iki dizin ve öğelerin listenin dahili dizisinde her seferinde bir tane tutulması için taşıma ihtiyacı vardır. Verimlilik, Tom'un algoritmasıyla aynı olan O (n) 'dir LinkedList
. Her iki sürümde de, dizeler için HashSet anahtarını hesaplama zamanı hakimdir. Bu ne zaman kullanılacağına iyi bir örnek değildir LinkedList
.
Yerleşik dizinli erişim, sıralama (ve bu ikili aramanın ardından) ve "ToArray ()" yöntemine ihtiyacınız olduğunda, List komutunu kullanmalısınız.
Temel olarak, List<>
.NET'te bir dizi üzerinde bir sarıcıdır . A LinkedList<>
bağlantılı bir listedir . Dolayısıyla, bir dizi ile bağlantılı bir liste arasındaki fark nedir ve bağlantılı bir liste yerine bir dizi ne zaman kullanılmalıdır. Muhtemelen, kararınızda kullanılacak en önemli iki faktör aşağıdakilere inecektir:
Bu Tono Nam'dan uyarlanmıştır birkaç yanlış ölçümü düzelten kabul edilen cevabından .
Test:
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);
}
Ve kod:
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();
}
}
}
Sonuçların, diğerlerinin burada belgelediği teorik performansa uygun olduğunu görebilirsiniz. Oldukça açık -LinkedList<T>
yerleştirme durumunda büyük zaman kazanır. Listenin ortasından kaldırılmak üzere test etmedim, ancak sonuç aynı olmalı. Tabii ki List<T>
O (1) rastgele erişim gibi daha iyi performans gösteren diğer alanları var.
LinkedList<>
Ne zaman kullanın
Token Stream
,.Diğer her şey için, kullanmak daha iyidir List<>
.
LinkedListNode<T>
Kodunuzdaki nesneleri izlerseniz listeyi ekleme / silme işlemleri için taramanız gerekmez . Bunu yapabiliyorsanız List<T>
, özellikle eklerin / çıkarmaların sık olduğu çok uzun listeler için kullanmaktan çok daha iyidir .
node.Value
Yukarıda belirtilen hususların çoğuna katılıyorum. Listenin de çoğu durumda daha bariz bir seçenek gibi göründüğüne katılıyorum.
Ancak, sadece daha iyi verimlilik için LinkedList listesi çok daha iyi bir seçim olduğu birçok örnek olduğunu eklemek istiyorum.
Umarım birisi bu yorumları faydalı bulur.
Burada çok fazla ortalama cevap var ...
Bazı bağlantılı liste uygulamaları, önceden atanmış düğümlerin altında yatan blokları kullanır. Bunu sabit süreden / doğrusal süreden daha fazla yapmazlarsa, bellek performansı zayıf olacağından ve önbellek performansı daha da kötü olacağından daha az önemlidir.
Aşağıdaki durumlarda bağlantılı listeleri kullan
1) İplik güvenliği istiyorsunuz. Daha iyi iplik güvenli algos oluşturabilirsiniz. Kilitleme maliyetleri eşzamanlı bir stil listesine hakim olacaktır.
2) Yapılar gibi büyük bir kuyruğunuz varsa ve her zaman sondan başka bir yeri kaldırmak veya eklemek istiyorsanız. 100.000'den fazla liste var ancak bu kadar yaygın değil.
LinkedList koleksiyonunun performansı ile ilgili benzer bir soru sordum ve Steven Cleary'nin Deque'nin C # uygulamasının bir çözüm olduğunu keşfetti . Kuyruk koleksiyonundan farklı olarak, Deque öğelerin ön ve arkada hareket ettirilmesine izin verir. Bağlantılı listeye benzer, ancak performansı artırılmıştır.
Deque
olduğu "bağlantılı listeye benzer fakat gelişmiş performans ile" . Bu ifadeyi nitelemek edin: Deque
daha iyi performans LinkedList
, özel kodu için . Bağlantınızı takiben, iki gün sonra Ivan Stoev'den bunun LinkedList'in verimsizliği değil, kodunuzdaki verimsizlik olduğunu öğrendiğinizi görüyorum. (Bu LinkedList bir verimsizlik olmuştu bile, o deque daha verimli olduğuna dair genel bir açıklama haklılaştırmaz, sadece özel durumlarda.)