web-dev-qa-db-ja.com

Collections.addAllがc.addAllよりも高速であるはずなのはなぜですか

Java APIのドキュメントによると 以下についてCollections.addAll

この便利なメソッドの動作はc.addAll(Arrays.asList(elements))の動作と同じですが、このメソッドはほとんどの実装で著しく高速に実行される可能性があります。

だから私が正しく理解すれば、a)はb)より遅いです:

a)

Collection<Integer> col = new ArrayList<Integer>();
col.addAll(Arrays.asList(1, 2, 3, 4, 5));

b)

Collection<Integer> col = new ArrayList<Integer>();
// Collections.addAll(col, Arrays.asList(1, 2, 3, 4, 5)); <-- won't compile
Collections.addAll(col, 1, 2, 3, 4, 5);

誰かが私に説明できますか、それはなぜですか?

編集:修正されたコード例。 thxから polygenelubricants

37
dertoni

それらの2つを詳しく見てみましょう。

_// a)
col.addAll(Arrays.asList(1, 2, 3, 4, 5));
_

ここで何が起こるかです:

  1. varags + autoboxingは_Integer[]_を作成します
  2. _Arrays.asList_は、配列に基づく_List<Integer>_を作成します
  3. addAll _Collection<Integer>_を使用して_Iterator<Integer>_を反復します
_// b)
Collections.addAll(col, 1, 2, 3, 4, 5);
_

ここで何が起こるかです:

  1. varargs + autoboxingは_Integer[]_を作成します
  2. addAllは配列を反復処理します(_Iterable<Integer>_の代わりに)

_b)_の方が高速であることがわかります。

  • _Arrays.asList_の呼び出しはスキップされます。つまり、中間Listは作成されません。
  • 要素は配列で与えられるので(varargsメカニズムのおかげで)、それらの反復処理は Iterator を使用するよりも速くなる可能性があります。

そうは言っても、プロファイリングが別の方法で示していない限り、その差は「有意」である可能性は低いです。時期尚早に最適化しないでください。 Javaコレクションフレームワーククラスは配列よりも遅い場合がありますが、ほとんどのアプリケーションでは十分に機能します。

APIリンク

こちらもご覧ください

関連する質問


概要

  • 配列から要素を追加する場合は、Collections.addAll(col, arr) を使用できます。
    • 可変引数も配列を使用して行われることに注意してください
  • Collectionから要素を追加する場合は、col.addAll(otherCol) を使用します。
    • する[〜#〜]しない[〜#〜]例: Collections.addAll(col, otherCol.toArray())
      • このような回り道は遅くなる可能性があります!
  • 一方が他方よりもはるかに速いというわけではありません
    • 現在の状況で不必要なステップをスキップすることです
50

それがより速くなるかもしれない唯一の理由は、それが配列をラップするだけなので比較的安価であるべきであるArrays.asListへの呼び出しを回避することです。 LinkedListなどの一部のコレクション実装は、要素を追加する前に、渡されたコレクションを配列に変換し直し、オーバーヘッドを追加します。

一方、ArrayList.addAllは、要素を追加する前に必要なスペースを1回割り当てるため、Collections.addAllがバッキング配列の複数のサイズ変更を必要とする場合ははるかに高速になります。

要約すると、Collections.addAllは、いくつかの要素のみをコレクションに繰り返し追加する方が高速である可能性がありますが、このケースがパフォーマンスのボトルネックになることはないと思います。

3
Jörn Horstmann

@polygenelubricantsで言及されている各ステップの(概算)関連する時間複雑性コスト関数を次に示します。

a)引数リストの3回の反復〜= C(3N)

b)引数リストの2回の反復〜= C(2N)

明らかに、両方ともO(N)ですが、アプローチb)は、アプローチa)と比較して〜Nの比較を保存します。うまくいけば、これは定量的な説明に興味を持っていた人には役に立ちます。

1
tep

(SE Platform 6でビルドしましょう)

それはすべて実際のコレクションの実装に依存します。あなたの例では

Collection<Integer> col = new ArrayList<Integer>();

addAllArrayListメソッドはオーバーライドされます。反復は一切ありません。ここにソースがあります:

_public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
    int numNew = a.length;
ensureCapacity(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
return numNew != 0;
}
_

お気づきかもしれませんが、c.toArray()も実際の実装によって異なります。繰り返しますが、あなたの場合、Arrays.asList()ArrayListとなり、toArray()メソッドのバージョンは次のようになります。

_public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}
_

この静的メソッドは_System.arraycopy_に基づいています

したがって、ここで扱うのは_System.arraycopy_の2つの呼び出しです。これは、現在のオペレーティングシステム用に特に最適化されたネイティブメソッドであるため、それほど悪くありません。

それで、それをすべてpolygenelubricantsのスタイルで要約すると:

  1. varags + autoboxingは_Integer[]_を作成します
  2. _Arrays.asList_は_ArrayList<Integer>_を作成します
  3. _ArrayList.addAll_呼び出しSystem.arraycopy(size) x2、サイズ= 5

配列内の5つのオブジェクトの場合、_Collections.addAll_の方が高速です。しかし、そのような小さな配列サイズとは無関係です。一方、たとえば、配列内の100k要素の場合、col.addAll(Arrays.asList(...))ははるかに効率的です。これは、ネイティブメソッドの場合、100k回の反復/コピー操作ではなく、単一のmemcpy/memmoveを処理するためです。 。

そして再び、それはすべてコレクションの実装に依存します。たとえばLinkedListは、期待どおりに反復します。

1
adiosmsu