私はいつも単純に使うことをずっとしてきました:
List<String> names = new ArrayList<>();
私は portability の型名としてインタフェースを使用しているので、このような質問をしたときにコードを書き直すことができます。
LinkedList
を ArrayList
の上でいつ使用すべきか、またその逆の場合
概要ArrayList
name__とArrayDeque
name__は、manyでは、LinkedList
name__よりも多くのユースケースに適しています。よくわからない場合は、ArrayList
name__で始めてください。
LinkedList
name__とArrayList
name__は、Listインターフェイスの2つの異なる実装です。 LinkedList
name__は、二重にリンクされたリストで実装します。 ArrayList
name__は、動的にサイズ変更する配列で実装します。
標準のリンクリストおよび配列操作と同様に、さまざまなメソッドには異なるアルゴリズムランタイムがあります。
LinkedList<E>
の場合
get(int index)
はO(n)(平均でn/4ステップ)add(E element)
はO(1)add(int index, E element)
はO(n)(平均でn/4ステップ)ですが、O(1) when index = 0
<- -LinkedList<E>
の主な利点remove(int index)
はO(n)(平均でn/4ステップ)Iterator.remove()
はO(1)です。 <--- LinkedList<E>
の主な利点ListIterator.add(E element)
はO(1)これはLinkedList<E>
の主な利点の1つです注:操作の多くは、平均でn/4ステップ、定数最良の場合のステップ数(例:インデックス= 0)、およびn/2最悪の場合のステップ(リストの中央)
ArrayList<E>
の場合
get(int index)
はO(1) <--- ArrayList<E>
の主な利点add(E element)
はO(1)償却ですが、O(n)最悪の場合は配列のサイズを変更してコピーする必要があるためですadd(int index, E element)
はO(n)(平均でn/2ステップ)remove(int index)
はO(n)(平均でn/2ステップ)Iterator.remove()
はO(n)(平均でn/2ステップ)ListIterator.add(E element)
はO(n)(平均でn/2ステップ)注:多くの操作には、平均でn/2ステップ、定数最良の場合のステップ数(リストの最後)、nが必要です。最悪の場合の手順(リストの先頭)
LinkedList<E>
は、一定時間の挿入または削除を許可しますsing iteratorsですが、要素の順次アクセスのみです。つまり、リストを前後に移動できますが、リスト内の位置を見つけるには、リストのサイズに比例した時間がかかります。 Javadocは、"リストにインデックスを付ける操作は、リストの先頭または末尾のどちらか近い方を走査します"であるため、これらのメソッドはO(n)(- n/4ステップ)、平均してO(1)index = 0
の場合。
一方、ArrayList<E>
は、高速のランダム読み取りアクセスを許可するため、一定の時間で任意の要素を取得できます。ただし、最後以外の場所で追加または削除を行うには、隙間を埋めたり隙間を埋めるために、後者のすべての要素をシフトする必要があります。また、基礎となる配列の容量よりも多くの要素を追加すると、新しい配列(サイズの1.5倍)が割り当てられ、古い配列が新しい配列にコピーされるため、ArrayList
name__への追加はO( n)最悪の場合、平均的に一定。
そのため、実行する操作に応じて、実装を選択する必要があります。どちらの種類のListを繰り返し処理することも、実質的に同じくらい安価です。 (ArrayList
name__を反復処理することは技術的に高速ですが、パフォーマンスに非常に敏感なことをしているのでなければ、これについて心配する必要はありません-それらは両方とも定数です。)
LinkedList
name__を使用する主な利点は、既存のイテレーターを再利用して要素を挿入および削除するときに発生します。これらの操作は、リストをローカルでのみ変更することでO(1)で実行できます。配列リストでは、配列の残りの部分をmoved(つまりコピー)にする必要があります。一方、LinkedList
name__でシークすると、最悪の場合はO(n)(n/2ステップ)のリンクをたどることになりますが、ArrayList
name__では数学的に計算され、O(1)でアクセスされます。
リストの先頭に追加または削除するときにLinkedList
name__を使用するもう1つの利点は、これらの操作がO(1)である一方で、O(n)であるためです。 ArrayList
name__。 ArrayDeque
name__は、LinkedList
name__に代わる適切な代替手段である可能性があることに注意してください。ただし、List
name__ではありません。
また、大きなリストがある場合は、メモリ使用量も異なることに注意してください。 LinkedList
name__の各要素には、次の要素と前の要素へのポインタも格納されるため、オーバーヘッドが大きくなります。 ArrayLists
name__にはこのオーバーヘッドはありません。ただし、ArrayLists
name__は、要素が実際に追加されているかどうかに関係なく、容量に割り当てられているだけのメモリを占有します。
ArrayList
name__のデフォルトの初期容量はかなり小さい(Java 1.4から1.8の10)。しかし、基礎となる実装は配列であるため、多くの要素を追加する場合は配列のサイズを変更する必要があります。多くの要素を追加することがわかっているときにサイズ変更の高コストを回避するには、初期容量を増やしてArrayList
name__を構築します。
これまでのところ、LinkedList
はArrayList
より「もっとたくさん」であるという一般的なコンセンサスを除いて、これらのリストそれぞれのメモリ使用量に誰も対処していないようです。 。
相対システムでは、参照は32ビットまたは64ビット(nullの場合でも)なので、32ビットおよび64ビットのLinkedLists
およびArrayLists
に4つのデータセットを含めました。
注: ArrayList
行に表示されるサイズは、 トリムされたリスト用です。 - 実際には、ArrayList
内のバッキング配列の容量は通常、現在の要素数よりも大きくなります。
注2: (thanks BeeOnRope) 現在、JDK 6以降のバージョンではCompressedOopsがデフォルトになっているため、64ビットマシンの場合、下の値は基本的に32ビット版と同じです。消して。
その結果は、特に非常に高い要素数の場合、LinkedList
がArrayList
よりもはるかに多いことを明確に示しています。記憶が要因であるならば、LinkedLists
を避けてください。
私が使った式は、私が間違ったことをしたかどうか私に教えてください、そして私はそれを直します。 32または64ビットシステムの場合、 'b'は4または8で、 'n'は要素数です。 modの理由は、Javaのすべてのオブジェクトが、すべて使用されているかどうかにかかわらず、8バイトの倍数のスペースを占有するためです。
配列リスト:
ArrayList object header + size integer + modCount integer + array reference + (array oject header + b * n) + MOD(array oject, 8) + MOD(ArrayList object, 8) == 8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8) + MOD(8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8), 8)
LinkedList:
LinkedList object header + size integer + modCount integer + reference to header + reference to footer + (node object overhead + reference to previous element + reference to next element + reference to element) * n) + MOD(node object, 8) * n + MOD(LinkedList object, 8) == 8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n + MOD(8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n, 8)
ArrayList
はあなたが望むものです。 LinkedList
はほとんどの場合(パフォーマンス)バグです。
なぜLinkedList
が吸うのか:
ArrayList
が使われた場合よりもアルゴリズムO(n)が遅くなります。ArrayList
と同じであっても、とにかくかなり遅くなるでしょう。LinkedList
を見るのは不快です。約10年間、非常に大規模なSOA Webサービスで運用パフォーマンスエンジニアリングを行ってきた人として、ArrayListよりもLinkedListの動作を好みます。 LinkedListの定常状態のスループットは悪化するため、ハードウェアの追加購入につながる可能性があります-圧力下でのArrayListの動作は、クラスター内のアプリがほぼ同期してアレイを拡張し、大きなアレイサイズでは応答性が不足する可能性がありますアプリと停止中に、圧力下で、これは壊滅的な動作です。
同様に、アプリのスループットはデフォルトのスループットガベージコレクターから向上させることができますが、10GBのヒープを持つJavaアプリを取得すると、フルGCの間にアプリを25秒間ロックアップすることができます。 SOAアプリのタイムアウトと失敗、およびSLAが頻繁に発生する場合の打撃。 CMSコレクターはより多くのリソースを使用し、同じ生のスループットを達成していませんが、より予測可能で待ち時間が短いため、より良い選択です。
ArrayListは、パフォーマンスがスループットのみを意味し、レイテンシを無視できる場合にのみ、パフォーマンスに適した選択肢になります。私の仕事での経験では、最悪の場合の待ち時間を無視することはできません。
Algorithm ArrayList LinkedList
seek front O(1) O(1)
seek back O(1) O(1)
seek to index O(1) O(N)
insert at front O(N) O(1)
insert at back O(1) O(1)
insert after an item O(N) O(1)
ArrayListsは、追記型やアペンダには適していますが、前面または中央からの追加/削除はできません。
ええ、これは古代の質問ですが、2セントを投入します。
LinkedListはほぼ常にパフォーマンス的に間違った選択です。 LinkedListが要求される非常に特定のアルゴリズムがいくつかありますが、それらは非常にまれであり、アルゴリズムは通常、リスト内の要素を比較的すばやく挿入および削除するLinkedListの機能に依存します。 ListIteratorを使用します。
LinkedListがArrayListよりも優れている一般的な使用例が1つあります。それはキューの使用です。ただし、目標がパフォーマンスの場合、LinkedListの代わりにArrayBlockingQueueを使用することも検討する必要があります(事前にキューサイズの上限を決定でき、すべてのメモリを前もって割り当てる余裕がある場合)、または CircularArrayListの実装 。 (はい、2001年からですので、それを一般化する必要がありますが、最近のJVMの記事で引用されているものと同等のパフォーマンス比を得ました)
効率の問題です。 LinkedList
は要素の追加や削除は速いですが、特定の要素へのアクセスは遅いです。 ArrayList
は特定の要素にアクセスするのには速いですが、どちらかの端に追加するのは遅く、特に途中で削除するのは遅くなります。
正解または不正解:ローカルでtestを実行して自分で決めてください。
編集/削除はLinkedList
よりArrayList
の方が高速です。
ArrayList
に裏打ちされたArray
は、サイズを2倍にする必要があり、大容量アプリケーションではさらに悪くなります。
下記は各操作の単体テスト結果です。タイミングはナノ秒単位で示されています。
Operation ArrayList LinkedList
AddAll (Insert) 101,16719 2623,29291
Add (Insert-Sequentially) 152,46840 966,62216
Add (insert-randomly) 36527 29193
remove (Delete) 20,56,9095 20,45,4904
contains (Search) 186,15,704 189,64,981
これがコードです:
import org.junit.Assert;
import org.junit.Test;
import Java.util.*;
public class ArrayListVsLinkedList {
private static final int MAX = 500000;
String[] strings = maxArray();
////////////// ADD ALL ////////////////////////////////////////
@Test
public void arrayListAddAll() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
watch.start();
arrayList.addAll(stringList);
watch.totalTime("Array List addAll() = ");//101,16719 Nanoseconds
}
@Test
public void linkedListAddAll() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
watch.start();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(stringList);
watch.totalTime("Linked List addAll() = "); //2623,29291 Nanoseconds
}
//Note: ArrayList is 26 time faster here than LinkedList for addAll()
///////////////// INSERT /////////////////////////////////////////////
@Test
public void arrayListAdd() {
Watch watch = new Watch();
List<String> arrayList = new ArrayList<String>(MAX);
watch.start();
for (String string : strings)
arrayList.add(string);
watch.totalTime("Array List add() = ");//152,46840 Nanoseconds
}
@Test
public void linkedListAdd() {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
watch.start();
for (String string : strings)
linkedList.add(string);
watch.totalTime("Linked List add() = "); //966,62216 Nanoseconds
}
//Note: ArrayList is 9 times faster than LinkedList for add sequentially
/////////////////// INSERT IN BETWEEN ///////////////////////////////////////
@Test
public void arrayListInsertOne() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX + MAX / 10);
arrayList.addAll(stringList);
String insertString0 = getString(true, MAX / 2 + 10);
String insertString1 = getString(true, MAX / 2 + 20);
String insertString2 = getString(true, MAX / 2 + 30);
String insertString3 = getString(true, MAX / 2 + 40);
watch.start();
arrayList.add(insertString0);
arrayList.add(insertString1);
arrayList.add(insertString2);
arrayList.add(insertString3);
watch.totalTime("Array List add() = ");//36527
}
@Test
public void linkedListInsertOne() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(stringList);
String insertString0 = getString(true, MAX / 2 + 10);
String insertString1 = getString(true, MAX / 2 + 20);
String insertString2 = getString(true, MAX / 2 + 30);
String insertString3 = getString(true, MAX / 2 + 40);
watch.start();
linkedList.add(insertString0);
linkedList.add(insertString1);
linkedList.add(insertString2);
linkedList.add(insertString3);
watch.totalTime("Linked List add = ");//29193
}
//Note: LinkedList is 3000 nanosecond faster than ArrayList for insert randomly.
////////////////// DELETE //////////////////////////////////////////////////////
@Test
public void arrayListRemove() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
arrayList.addAll(stringList);
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
arrayList.remove(searchString0);
arrayList.remove(searchString1);
watch.totalTime("Array List remove() = ");//20,56,9095 Nanoseconds
}
@Test
public void linkedListRemove() throws Exception {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(Arrays.asList(strings));
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
linkedList.remove(searchString0);
linkedList.remove(searchString1);
watch.totalTime("Linked List remove = ");//20,45,4904 Nanoseconds
}
//Note: LinkedList is 10 millisecond faster than ArrayList while removing item.
///////////////////// SEARCH ///////////////////////////////////////////
@Test
public void arrayListSearch() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
arrayList.addAll(stringList);
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
arrayList.contains(searchString0);
arrayList.contains(searchString1);
watch.totalTime("Array List addAll() time = ");//186,15,704
}
@Test
public void linkedListSearch() throws Exception {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(Arrays.asList(strings));
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
linkedList.contains(searchString0);
linkedList.contains(searchString1);
watch.totalTime("Linked List addAll() time = ");//189,64,981
}
//Note: Linked List is 500 Milliseconds faster than ArrayList
class Watch {
private long startTime;
private long endTime;
public void start() {
startTime = System.nanoTime();
}
private void stop() {
endTime = System.nanoTime();
}
public void totalTime(String s) {
stop();
System.out.println(s + (endTime - startTime));
}
}
private String[] maxArray() {
String[] strings = new String[MAX];
Boolean result = Boolean.TRUE;
for (int i = 0; i < MAX; i++) {
strings[i] = getString(result, i);
result = !result;
}
return strings;
}
private String getString(Boolean result, int i) {
return String.valueOf(result) + i + String.valueOf(!result);
}
}
ArrayList
は本質的に配列です。 LinkedList
は二重リンクリストとして実装されています。
get
はかなり明確です。 ArrayList
はindexを使用してランダムアクセスを許可するため、ArrayList
の場合はO(1)になります。最初にインデックスを見つける必要があるため、LinkedList
に対するO(n)。注:add
とremove
には異なるバージョンがあります。
LinkedList
は追加と削除のほうが速くなりますが、取得は遅くなります。簡単に言うと、LinkedList
は次のような場合に推奨されます。
=== ArrayList ===
=== LinkedList ===
追加(E e)
add(int index、E要素)
これは programcreek.com からの図です(add
とremove
は最初の型です。つまり、リストの最後に要素を追加し、リストの指定された位置にある要素を削除します)。
1) 検索: ArrayList検索操作は、LinkedList検索操作と比較してかなり高速です。 ArrayListのget(int index)は、O(1)のパフォーマンスを示します。一方、LinkedListのパフォーマンスはO(n)です。
理由: ArrayListは、暗黙的に配列データ構造を使用するため、要素のインデックスベースのシステムを維持します。これにより、リスト内の要素の検索が高速になります。一方、LinkedListは、要素を検索するためにすべての要素を横断することを要求する二重リンクリストを実装しています。
2) 削除: LinkedListの削除操作でO(1)のパフォーマンスが得られ、ArrayListでは可変のパフォーマンスが得られます。最悪の場合O(n)そして最善の場合(最後の要素を削除している間)はO(1)です。
結論: LinkedList要素の削除はArrayListに比べて高速です。
理由: LinkedListの各要素は、リスト内の両方の近隣要素を指す2つのポインタ(アドレス)を保持しています。それ故、除去は、除去されることになるノードの2つの隣接ノード(要素)内のポインタ位置の変更のみを必要とする。 ArrayListでは、削除された要素によって作成されたスペースを埋めるためにすべての要素を移動する必要があります。
3) パフォーマンスの挿入: LinkedListのaddメソッドはO(1)のパフォーマンスを示し、ArrayListは最悪の場合O(n)を示します。その理由はremoveの説明と同じです。
4) メモリオーバーヘッド: ArrayListはインデックスと要素データを保持し、LinkedListは要素データと隣接ノードの2つのポインタを保持するため、LinkedListではメモリ消費量が比較的多くなります。
これらのクラスの間には いくつかの類似点 があります。
ArrayListとLinkedListはどちらもListインタフェースの実装です。これらはどちらも要素の挿入順序を維持します。つまり、ArrayListとLinkedList要素を表示している間、結果セットは要素がListに挿入された順序と同じ順序になります。これらのクラスはどちらも非同期であり、Collections.synchronizedListメソッドを使用して明示的に同期させることができます。反復子が作成されてから、反復子自身のremoveまたはaddメソッド以外の方法で、反復子はConcurrentModificationExceptionをスローします。
LinkedListを使用する場合とArrayListを使用する場合
1)上で説明したように、挿入と削除の操作は、ArrayList(O(n))と比較してLinkedListのパフォーマンスが良い(O(1))。そのため、アプリケーションで頻繁に追加や削除を行う必要がある場合は、LinkedListが最適です。
2)検索(getメソッド)操作はArrayList(O(1))では高速ですが、LinkedList(O(n))では高速ではないため、追加および削除操作や検索操作の要件が少ない場合は、ArrayListを使用するのが最善です。
LinkedListの作者、Joshua Bloch:
実際にLinkedListを使用している人はいますか?私はそれを書きました、そして私はそれを使いません。
リンク: https://Twitter.com/joshbloch/status/583813919019573248
他の答えほど有益ではないという答えを残念に思いますが、私はそれが最も面白くて自明であると思いました。
ArrayList
はランダムにアクセス可能ですが、LinkedList
は要素を展開したり要素を削除したりするのに本当に安価です。ほとんどの場合、ArrayList
は問題ありません。
大規模なリストを作成してボトルネックを測定したのでなければ、おそらく違いについて心配する必要はないでしょう。
あなたのコードがadd(0)
とremove(0)
を持っているならば、LinkedList
を使ってください、そしてそれはaddFirst()
とremoveFirst()
メソッドです。それ以外の場合はArrayList
を使用してください。
そしてもちろん、 Guava の ImmutableList はあなたの親友です。
これは古い投稿ですが、LinkedList
がDeque
を実装していると言っている人が誰もいないとは信じられません。 Deque
(とQueue
)のメソッドを見てください。公正な比較が必要な場合は、LinkedList
に対してArrayDeque
を実行し、機能ごとの比較を行ってください。
これがArrayList
とLinkedList
、そしてCopyOnWrite-ArrayList
のBig-O記法です。
配列リスト
get O(1)
add O(1)
contains O(n)
next O(1)
remove O(n)
iterator.remove O(n)
LinkedList
get O(n)
add O(1)
contains O(n)
next O(1)
remove O(1)
iterator.remove O(1)
CopyOnWrite-ArrayList
get O(1)
add O(n)
contains O(n)
next O(1)
remove O(n)
iterator.remove O(n)
これらに基づいて、あなたは何を選ぶべきか決める必要があります。 :)
LinkedListとArrayListを比較してみましょう。以下のパラメータ:
ArrayList はリストインタフェースのサイズ変更可能な配列の実装です。
LinkedList は、リストインタフェースの二重リンクリストの実装です。
ArrayList get(int index)操作は一定時間、つまりO(1)で実行されます。
LinkedList get(int index)操作の実行時間はO(n)です。
ArrayList の背後にある理由はLinkedListより速いのに対して、ArrayListは内部的に配列データ構造を使用しているため、ArrayListはその要素に対してインデックスベースのシステムを使用しているためです。
LinkedList は、指定された要素インデックスのノードを取得するために先頭または末尾(どちらか近いほう)から反復するため、その要素に対するインデックスベースのアクセスを提供しません。
LinkedList への挿入は、ArrayListに比べて一般的に高速です。 LinkedListでは、追加または挿入はO(1)操作です。
ArrayList の場合、配列がいっぱいの場合、つまり最悪の場合、配列のサイズを変更して要素を新しい配列にコピーするための追加コストが発生します。それはO(1)です。
LinkedListにおける削除操作は一般にArrayListと同じ、すなわちO(n)である。
LinkedList には、2つのオーバーロードされたremoveメソッドがあります。 1つは、リストの先頭を削除し、一定時間O(1)で実行されるパラメータなしのremove()です。 LinkedListのもう1つのオーバーロードされたremoveメソッドはremove(int)またはremove(Object)です。これはObjectまたはパラメータとして渡されたintを削除します。このメソッドは、Objectが見つかるまでLinkedListをたどり、元のリストからのリンクを解除します。したがって、このメソッドの実行時間はO(n)です。
ArrayList remove(int)メソッドでは、古い配列から新しい更新された配列に要素をコピーする必要があるため、その実行時間はO(n)です。
LinkedList descendingIterator()を使用して逆方向に反復することができます。
ArrayList にdescendingIterator()がないので、逆方向にArrayListを反復処理するために独自のコードを書く必要があります。
コンストラクタがオーバーロードされていない場合、 ArrayList は初期容量10の空のリストを作成します。
LinkedList は、初期容量なしで空リストを作成するだけです。
LinkedList内のノードは次および前のノードのアドレスを維持する必要があるため、 LinkedList のメモリオーバーヘッドはArrayListと比較して大きくなります。しながら
ArrayList では、各インデックスは実際のオブジェクト(データ)のみを保持します。
TL; DR 現代のコンピュータアーキテクチャのため、ArrayList
はほぼすべてのユースケースで非常に効率的になります - したがって、LinkedList
は非常にユニークで極端な場合を除いて避けるべきです。
理論的には、LinkedListにはadd(E element)
に対するO(1)があります。
リストの途中に要素を追加することも非常に効率的です。
LinkedListは キャッシュ敵対者 データ構造であるため、実際のやり方は非常に異なります。パフォーマンスのPOVから - LinkedList
がCache-friendlyArrayList
よりもパフォーマンスが良い場合がほとんどありません。
これは、ランダムな位置に要素を挿入するベンチマークテストの結果です。ご覧のとおり、配列リストの方がはるかに効率的ですが、理論的にはリストの中央に挿入するたびに配列のn後の要素を「移動」する必要があります(値が小さいほど良い)。
より新しい世代のハードウェア(より大きく、より効率的なキャッシュ)で作業する - 結果はさらに決定的です。
LinkedListが同じ仕事を成し遂げるのにより多くの時間がかかります。 ソースソースコード
これには主に2つの理由があります。
主に - LinkedList
のノードがメモリ全体にランダムに散らばっていることを示します。 RAM( "Random Access Memory")は実際にはランダムではなく、メモリブロックをキャッシュにフェッチする必要があります。この操作には時間がかかり、そのようなフェッチが頻繁に発生する場合、キャッシュ内のメモリページを常に交換する必要があります - >キャッシュミス - >キャッシュは効率的ではありません。 ArrayList
要素は連続メモリに格納されます。現代のCPUアーキテクチャが最適化しているのはまさにその通りです。
2次 逆方向ポインタを保持するためにはLinkedList
が必要です。これは、ArrayList
と比較して、格納された値あたり3倍のメモリ消費を意味します。
DynamicIntArray 、btwは、オブジェクトではなくInt
(プリミティブ型)を保持するカスタムのArrayList実装です。したがって、すべてのデータは実際には隣接して格納されます。したがって、さらに効率的です。
覚えておくべき重要な要素は、メモリブロックをフェッチするコストが、単一のメモリセルにアクセスするコストよりも重要であるということです。これが、リーダーの1MBのシーケンシャルメモリが、異なるメモリブロックからこの量のデータを読み取るよりも最大で400倍高速になる理由です。
Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns 14x L1 cache
Mutex lock/unlock 25 ns
Main memory reference 100 ns 20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy 3,000 ns 3 us
Send 1K bytes over 1 Gbps network 10,000 ns 10 us
Read 4K randomly from SSD* 150,000 ns 150 us ~1GB/sec SSD
Read 1 MB sequentially from memory 250,000 ns 250 us
Round trip within same datacenter 500,000 ns 500 us
Read 1 MB sequentially from SSD* 1,000,000 ns 1,000 us 1 ms ~1GB/sec SSD, 4X memory
Disk seek 10,000,000 ns 10,000 us 10 ms 20x datacenter roundtrip
Read 1 MB sequentially from disk 20,000,000 ns 20,000 us 20 ms 80x memory, 20X SSD
Send packet CA->Netherlands->CA 150,000,000 ns 150,000 us 150 ms
より明確にするために、リストの先頭に要素を追加するベンチマークを確認してください。これはユースケースで、理論的にはLinkedList
が本当に輝き、ArrayList
が悪い結果またはさらに悪いケースの結果を示すはずです。
注:これはC++ Std libのベンチマークですが、私の以前の経験ではC++とJavaの結果はよく似ています。 ソースコード
シーケンシャルなメモリの大部分をコピーすることは、現代のCPUによって最適化された操作です - 理論を変えて、実際にもArrayList
/Vector
をはるかに効率的にする
クレジット:ここに掲載されているすべてのベンチマークは KjellHedström によって作成されています。さらに多くのデータが 彼のブログにあります
上記の他の良い引数に加えて、ArrayList
はRandomAccess
を実装していますが、LinkedList
はQueue
インターフェースを実装しています。
それで、どういうわけか彼らは効率と振る舞いの違いで若干異なる問題に対処します(彼らの方法のリストを見てください)。
Javaチュートリアル - リストの実装 を参照してください。
配列リストは、基本的にアイテムなどを追加するためのメソッドを含む配列です(代わりにジェネリックリストを使うべきです)。これはインデクサーを介してアクセスできる項目の集まりです(例えば[0])。それはある項目から次の項目への進行を意味します。
リンクリストは、ある項目から次の項目への進行を指定します(項目a - >項目b)。配列リストでも同じ効果が得られますが、リンクリストでは、どの項目が前の項目に続くことになっているかが絶対的に示されます。
それはあなたがリストに対してもっと何をすることになるかによって異なります。
ArrayList
はインデックス付きの値にアクセスするのが速いです。オブジェクトを挿入または削除するときは、はるかに悪いです。
詳細については、配列とリンクリストの違いについての記事を読んでください。
私は回答を読みましたが、私が意見を聞くために共有したいArrayListの上にLinkedListを常に使用するシナリオが1つあります。
DBから取得したデータのリストを返すメソッドを使用するたびに、常にLinkedListを使用します。
私の理論的根拠は、私が得た結果の正確な数を正確に知ることは不可能であるため、(容量と実際の要素数の違いによるArrayListのように)メモリが無駄にならず容量を複製します。
ArrayListに関しては、配列の重複をできるだけ少なくするために、少なくとも初期容量を持つコンストラクタを常に使用することに同意します。
リンクリストの重要な機能(私は別の答えでは読みませんでした)は、2つのリストの連結です。配列の場合、これはO(n)(+いくつかの再割り当てのオーバーヘッド)で、リンクリストの場合、これはO(1)またはO(2)のみです。 ;-)
重要 :JavaのLinkedList
では、これは正しくありません。参照してください Javaでリンクリストのための高速な連結方法はありますか?
私は通常、その特定のリストに対して実行する操作の時間の複雑さに基づいて、一方を他方の上に使用します。
|---------------------|---------------------|--------------------|------------|
| Operation | ArrayList | LinkedList | Winner |
|---------------------|---------------------|--------------------|------------|
| get(index) | O(1) | O(n) | ArrayList |
| | | n/4 steps in avg | |
|---------------------|---------------------|--------------------|------------|
| add(E) | O(1) | O(1) | LinkedList |
| |---------------------|--------------------| |
| | O(n) in worst case | | |
|---------------------|---------------------|--------------------|------------|
| add(index, E) | O(n) | O(n) | LinkedList |
| | n/2 steps | n/4 steps | |
| |---------------------|--------------------| |
| | | O(1) if index = 0 | |
|---------------------|---------------------|--------------------|------------|
| remove(index, E) | O(n) | O(n) | LinkedList |
| |---------------------|--------------------| |
| | n/2 steps | n/4 steps | |
|---------------------|---------------------|--------------------|------------|
| Iterator.remove() | O(n) | O(1) | LinkedList |
| ListIterator.add() | | | |
|---------------------|---------------------|--------------------|------------|
|--------------------------------------|-----------------------------------|
| ArrayList | LinkedList |
|--------------------------------------|-----------------------------------|
| Allows fast read access | Retrieving element takes O(n) |
|--------------------------------------|-----------------------------------|
| Adding an element require shifting | o(1) [but traversing takes time] |
| all the later elements | |
|--------------------------------------|-----------------------------------|
| To add more elements than capacity |
| new array need to be allocated |
|--------------------------------------|
ArrayList
とLinkedList
はどちらもList interface
を実装しており、それらのメソッドと結果はほぼ同じです。ただし、要件によって違いがあり、違いはありません。
1)Search:
ArrayList
検索操作は、LinkedList
検索操作に比べてかなり高速です。 ArrayList
のget(int index)
はO(1)
のパフォーマンスを示し、LinkedList
のパフォーマンスはO(n)
です。
Reason:
ArrayList
は、配列データ構造を暗黙的に使用するので、要素のインデックスベースのシステムを維持します。これにより、リスト内の要素の検索が高速になります。反対にLinkedList
は要素を検索するためにすべての要素を横断することを要求する二重リンクリストを実装しています。
2)Deletion:
LinkedList
remove操作はO(1)
性能を与えますが、ArrayList
は可変性能を与えます:O(n)
最悪の場合(最初の要素を削除する間)そしてO(1)
(最後の要素を削除する間)。
結論:LinkedList要素の削除は ArrayListに比べて速いです。
理由:LinkedListの各要素は、リスト内の両方の隣接要素を指す2つのポインタ(アドレス)を保持しています。それ故、除去は、除去されることになるノードの2つの隣接ノード(要素)内のポインタ位置の変更のみを必要とする。 ArrayListでは、削除された要素によって作成されたスペースを埋めるためにすべての要素を移動する必要があります。
3)Inserts Performance:
LinkedList
addメソッドはO(1)
パフォーマンスを与えますが、ArrayList
は最悪の場合O(n)
を与えます。理由は削除の説明と同じです。
4)Memory Overhead:
ArrayList
はインデックスと要素データを保持し、LinkedList
は要素データと隣接ノードへの2つのポインタを保持します。
そのため、LinkedListではメモリ消費が比較的多くなります。
iterator
とlistIterator
はfail-fast
です(リストがイテレータの作成後いつでも構造的に変更されている場合は、iterator’s
自身のremoveメソッドまたはaddメソッドを使用しない限り、イテレータはthrow
をConcurrentModificationException
にします)。ArrayList(O(n))
と比較してLinkedList
で良いパフォーマンス(O(1))
を与えます。 したがって、アプリケーション内で頻繁に追加や削除を行う必要がある場合は、LinkedListが最適です。
get method
)操作はArraylist (O(1))
では高速ですがLinkedList (O(n))
では高速ではありません したがって、追加や削除の操作が少なく、検索操作の要件が多い場合は、ArrayListを使用することをお勧めします。
ArrayListの操作get(i)はLinkedListよりも高速です。
ArrayList: Listインタフェースのサイズ変更可能な配列の実装
LinkedList: ListインターフェイスとDequeインターフェイスの二重リンクリストの実装
リストにインデックスを付ける操作は、指定されたインデックスに近い方のリストを最初から最後までトラバースします。
ArrayListとLinkedListには、それぞれ長所と短所があります。
次のノードへのポインタを使用するLinkedListと比較して、ArrayListは連続したメモリアドレスを使用します。そのため、ArrayListで要素を検索したい場合は、LinkedListを使用してn回反復するよりも高速です。
一方、LinkedListでの挿入と削除はポインタを変更するだけなので、はるかに簡単です。一方ArrayListでは、挿入または削除にシフト操作を使用する必要があります。
アプリ内で頻繁に検索操作を行う場合は、ArrayListを使用してください。頻繁に挿入や削除がある場合は、LinkedListを使用してください。
1)基礎となるデータ構造
ArrayListとLinkedListの最初の違いは、LinkedListがLinkedListによって支えられているのに対し、ArrayListはArrayによって支えられているという事実です。これにより、パフォーマンスがさらに向上します。
2)LinkedListはDequeを実装しています
ArrayListとLinkedListのもう1つの違いは、Listインタフェースとは別に、LinkedListはDequeインタフェースも実装しています。これは、add()およびpoll()、その他いくつかのDeque関数に対する先入れ先出し操作を提供します。 3)ArrayListへの要素の追加ArrayListへの要素の追加は、Arrayのサイズ変更を引き起こさない場合はO(1)操作で、その場合はO(log(n))になります。一方、LinkedListに要素を追加するのはO(1)操作です。ナビゲーションは不要です。
4)位置からの要素の削除
特定のインデックスから要素を削除するには、 remove(index)を呼び出すことで、ArrayListはO(n)に近づけるコピー操作を実行します。一方、LinkedListはそのポイントまでたどる必要があるため、O(n/2)にもなります。近接に基づいてどちらの方向からも。
5)ArrayListまたはLinkedListの繰り返し
反復は、LinkedListとArrayListの両方に対するO(n)操作です。ここで、nは要素の数です。
6)位置から要素を取り出す
Get(index)操作は、ArrayListではO(1)、LinkedListではO(n/2)です。これは、そのエントリまでトラバースする必要があるためです。ただし、Big O表記ではO(n/2)はO(n)にすぎません。定数は無視されるためです。
7)メモリー
LinkedListは、ラッパーオブジェクトEntryを使用します。Entryは、データと前後2つのノードを格納するための静的ネストクラスで、ArrayListは単にArrayにデータを格納するだけです。
そのため、ArrayListの場合、LinkedListよりもメモリ要件が少なくて済みます。ただし、Arrayが別のArrayにコンテンツをコピーするときにArrayがサイズ変更操作を実行する場合は例外です。
Arrayが十分に大きいと、その時点で大量のメモリを消費し、ガベージコレクションを引き起こす可能性があります。
ArrayListとLinkedListの間の上記のすべての違いから、remove()またはget()より頻繁にadd()操作を行う場合を除いて、ArrayListはほとんどすべての場合においてLinkedListよりも適しています。
リンクリストは内部的にそれらの位置の参照を保持しており、O(1)時間でアクセス可能であるため、特に開始または終了から要素を追加または削除する場合は、ArrayListよりリンクリストを変更する方が簡単です。
つまり、要素を追加したい位置にリンクリストをたどる必要はありません。その場合、追加はO(n)操作になります。たとえば、リンクリストの途中に要素を挿入または削除します。
私の意見では、Javaの実用的な目的のほとんどで、LinkedListよりもArrayListを使用します。
Remove()とinsert()の両方とも、ArrayListsとLinkedListsの両方に対して、ランタイム効率がO(n)です。しかし、線形処理時間の背後にある理由は、2つの非常に異なる理由から来ています。
ArrayListでは、O(1)の要素に到達しますが、実際に何かを削除または挿入すると、それはO(n)になります。これは、以下のすべての要素を変更する必要があるためです。
LinkedListでは、目的のインデックスに到達するまで最初から始めなければならないため、目的の要素に実際に到達するにはO(n)が必要です。 remove()については1つの参照を、insert()については2つの参照を変更するだけでよいため、実際の削除または挿入は一定です。
挿入と削除のどちらが速いかは、どちらが行われるかによって異なります。私たちが始めに近づくなら、LinkedListはより速くなるでしょう、なぜなら私たちは比較的少数の要素を通過しなければならないからです。終わりに近づくと、ArrayListの方が速くなります。一定時間でそこにたどり着き、それに続く残りのいくつかの要素を変更するだけでよいためです。 n個の要素を通過するほうがn個の値を移動するよりも速いため、正確に中央で処理するとLinkedListが速くなります。
おまけ:ArrayListに対してこれら2つのメソッドO(1)を作成する方法はありませんが、実際にはLinkedListsでこれを行う方法があります。リスト全体を調べて、途中で要素を削除および挿入したいとしましょう。通常、LinkedListを使用して各要素の最初から始めることになります。イテレータで作業している現在の要素を「保存」することもできます。イテレータの助けを借りて、LinkedListで作業しているときのremove()およびinsert()の効率をO(1)にします。これを唯一のパフォーマンス上の利点にするために、LinkedListが常にArrayListより優れている点に気付きました。
ArrayListはAbstractListを拡張し、Listインタフェースを実装します。 ArrayListは動的配列です。
それは基本的に配列の欠点を克服するために作成されたと言えます
LinkedListクラスはAbstractSequentialListを拡張し、List、Deque、およびQueueの各インタフェースを実装します。
パフォーマンスarraylist.get()
はO(1)ですが、linkedlist.get()
はO(n)です。 arraylist.add()
はO(1)で、linkedlist.add()
は0(1)です。arraylist.contains()
はO(n)で、linkedlist.contains()
はO(n)です。 arraylist.next()
はO(1)で、linkedlist.next()
はO(1)です。arraylist.remove()
はO(n)ですが、linkedlist.remove()
はO(1)です。
arraylistにiterator.remove()
はO(n)です
一方リンクリスト内 iterator.remove()
is O(1)
ここで見たテストの1つは、一度だけテストを実行することです。しかし、私が気付いたのは、これらのテストを何度も実行する必要があり、最終的にはそれらの時間が収束するということです。基本的にはJVMはウォームアップする必要があります。私の特定のユースケースでは、最後にアイテムを追加/削除して、約500アイテムにする必要がありました。私のテストでは、LinkedList
はより早く出て、リンクされたLinkedList
はおよそ50,000 NS、そしてArrayList
はおよそ90,000 NSになっています... give or take以下のコードを見てください。
public static void main(String[] args) {
List<Long> times = new ArrayList<>();
for (int i = 0; i < 100; i++) {
times.add(doIt());
}
System.out.println("avg = " + (times.stream().mapToLong(x -> x).average()));
}
static long doIt() {
long start = System.nanoTime();
List<Object> list = new LinkedList<>();
//uncomment line below to test with ArrayList
//list = new ArrayList<>();
for (int i = 0; i < 500; i++) {
list.add(i);
}
Iterator it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
long end = System.nanoTime();
long diff = end - start;
//uncomment to see the JVM warmup and get faster for the first few iterations
//System.out.println(diff)
return diff;
}