.toArray(new MyClass [0])または.toArray(new MyClass [myList.size()])?


176

ArrayListがあると仮定します

ArrayList<MyClass> myList;

そして、私はtoArrayを呼び出したいのですが、使用するパフォーマンス上の理由はありますか

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

以上

MyClass[] arr = myList.toArray(new MyClass[0]);

私は2番目のスタイルを選択します。これは冗長性が低いためです。また、空の配列が実際に作成されないことをコンパイラーが確認すると想定していますが、それが本当かどうか疑問に思っていました。

もちろん、99%の場合、どちらの方法でも違いはありませんが、通常のコードと最適化された内部ループの間で一貫したスタイルを維持したいと思います...


6
質問は、AlekseyShipilёv氏によるArrays of the Ancients of the Ancientsの新しいブログ投稿で解決されたようです!
glts

6
ブログ投稿から:「要点:toArray(new T [0])はより速く、より安全で、契約上よりクリーンに見えるため、今ではデフォルトの選択肢になっているはずです。」
DavidS 2016

回答:


109

直感的には、ホットスポット8の最速バージョンは次のとおりです。

MyClass[] arr = myList.toArray(new MyClass[0]);

jmhを使用してマイクロベンチマークを実行しました。結果とコードは以下のとおりです。空の配列を使用したバージョンは、事前にサイズ設定された配列を使用したバージョンより常に優れています。正しいサイズの既存の配列を再利用できる場合は、結果が異なる場合があることに注意してください。

ベンチマーク結果(マイクロ秒のスコア、小さい=良い):

Benchmark                      (n)  Mode  Samples    Score   Error  Units
c.a.p.SO29378922.preSize         1  avgt       30    0.025  0.001  us/op
c.a.p.SO29378922.preSize       100  avgt       30    0.155  0.004  us/op
c.a.p.SO29378922.preSize      1000  avgt       30    1.512  0.031  us/op
c.a.p.SO29378922.preSize      5000  avgt       30    6.884  0.130  us/op
c.a.p.SO29378922.preSize     10000  avgt       30   13.147  0.199  us/op
c.a.p.SO29378922.preSize    100000  avgt       30  159.977  5.292  us/op
c.a.p.SO29378922.resize          1  avgt       30    0.019  0.000  us/op
c.a.p.SO29378922.resize        100  avgt       30    0.133  0.003  us/op
c.a.p.SO29378922.resize       1000  avgt       30    1.075  0.022  us/op
c.a.p.SO29378922.resize       5000  avgt       30    5.318  0.121  us/op
c.a.p.SO29378922.resize      10000  avgt       30   10.652  0.227  us/op
c.a.p.SO29378922.resize     100000  avgt       30  139.692  8.957  us/op

参考までに、コード:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class SO29378922 {
  @Param({"1", "100", "1000", "5000", "10000", "100000"}) int n;
  private final List<Integer> list = new ArrayList<>();
  @Setup public void populateList() {
    for (int i = 0; i < n; i++) list.add(0);
  }
  @Benchmark public Integer[] preSize() {
    return list.toArray(new Integer[n]);
  }
  @Benchmark public Integer[] resize() {
    return list.toArray(new Integer[0]);
  }
}

同様の結果、完全な分析、およびディスカッションは、ブログの記事「古代の知恵の配列」にあります。要約すると、JVMおよびJITコンパイラーには、新しい適切なサイズの配列を安価に作成および初期化できるいくつかの最適化が含まれており、配列を自分で作成する場合、それらの最適化は使用できません。


2
非常に興味深いコメント。誰もこれについてコメントしていないことに驚いています。速度に関しては、ここでの他の回答と矛盾しているためだと思います。また興味深いことに、この人たちの評判は、他のすべての回答(er)を合わせたものよりもほとんど高くなっています。
Pimp Trizkit

余談です。また、MyClass[] arr = myList.stream().toArray(MyClass[]::new);..のベンチマークが遅くなると思います。また、配列宣言との違いのベンチマークを確認してください。MyClass[] arr = new MyClass[myList.size()]; arr = myList.toArray(arr);と:の違いのようにMyClass[] arr = myList.toArray(new MyClass[myList.size()]);...または違いはありませんか?これら二つはtoArray機能の出来事の外にある問題だと思います。しかしねえ!他の複雑な違いについては知りませんでした。
Pimp Trizkit

1
@PimpTrizkit確認済み:追加の変数を使用しても予想どおりの違いはありません。ストリームを使用すると、toArray直接呼び出した場合と比べて60%から100%時間がかかります(サイズが小さいほど、相対的なオーバーヘッドが大きくなります)
assylias

うわー、それは速い反応でした!ありがとう!いや、私はそれを疑った。ストリームへの変換は効率的に聞こえませんでした。しかし、あなたは決して知りません!
Pimp Trizkit

2
これと同じ結論がここで見つかりました:shipilev.net/blog/2016/arrays-wisdom-ancients
user167019

122

Java 5ArrayList以降、配列のサイズが適切である(または大きい)場合、配列はすでに入力されています。したがって

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

1つの配列オブジェクトを作成し、それを埋めて「arr」に戻します。一方

MyClass[] arr = myList.toArray(new MyClass[0]);

2つの配列が作成されます。2つ目は長さが0のMyClassの配列です。したがって、すぐに破棄されるオブジェクトのオブジェクトが作成されます。ソースコードが示唆する限り、コンパイラ/ JITはこれを最適化できないため、作成されません。さらに、長さがゼロのオブジェクトを使用すると、toArray()メソッド内でキャストが行われます。

ArrayList.toArray()のソースを参照してください。

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

最初の方法を使用して、1つのオブジェクトのみが作成され、(暗黙的であるにもかかわらず高価な)キャストを回避します。


1
2つのコメントは、誰かに興味があるかもしれません:1) LinkedList.toArray(T [] a)はさらに遅く(リフレクションを使用:Array.newInstance)、より複雑です。2)一方、JDK7リリースでは、通常、非常に遅くなったArray.newInstanceが通常の配列作成とほぼ同じ速さで実行されることを知り、非常に驚​​きました。
java.is.for.desktop

1
@ktaria sizeはArrayListのプライベートメンバーで、サイズを****サプライズ****で指定します。ArrayList SourceCode
MyPasswordIsLasercats

3
ベンチマークなしのパフォーマンスの推測は、些細な場合にのみ機能します。実際にnew Myclass[0]は高速です:shipilev.net/blog/2016/arrays-wisdom-ancients
Karol S

これは、もはやJDK6 +の有効な答えではありません
АнтонАнтонов

28

JetBrains Intellij Idea検査から:

コレクションを配列に変換する方法は2つあります:事前にサイズ設定された配列(c.toArray(new String [c.size()])など)を使用するか、空の配列(c.toArray(new String [など)を使用します。 0])

適切なサイズの配列を作成するために必要なリフレクションの呼び出しが非常に遅いため、以前のサイズの配列を使用する古いJavaバージョンでは推奨されていました。ただし、OpenJDK 6の最新のアップデート以降、この呼び出しが組み込まれたため、空の配列バージョンのパフォーマンスは、プリサイズバージョンと比較して同じになり、時にはさらに向上しました。また 、コレクションが操作中に同時に縮小された場合、サイズtoArray呼び出しの間でデータの競合が発生し、配列の最後に余分なnullが発生する可能性があるため、プリサイズの配列を渡すことは、同時または同期コレクションでは危険です 。

この検査により、空の配列(最新のJavaで推奨)を使用するか、事前にサイズ設定された配列(古いJavaバージョンまたは非HotSpotベースのJVMでは高速になる可能性があります)を使用して、統一されたスタイルに従うことができます。


このすべてがコピー/引用されたテキストである場合、それに応じてフォーマットし、ソースへのリンクを提供できますか?IntelliJインスペクションのおかげで実際にここに来ましたが、彼らのインスペクションとその背後にある理由をすべて調べるためのリンクにとても興味があります。
TimBüthe18年

3
ここでは、検査のテキストを確認することができます。github.com/JetBrains/intellij-community/tree/master/plugins/...
АнтонАнтонов


17

この場合、最新のJVMはリフレクトアレイの構築を最適化するため、パフォーマンスの違いはわずかです。このような定型コードでコレクションに2回名前を付けるのは良い考えではないので、最初の方法は避けます。2番目のもう1つの利点は、同期コレクションと並行コレクションで機能することです。最適化したい場合は、空の配列を再利用するか(空の配列は不変であり、共有できます)、またはプロファイラー(!)を使用します。


2
「空の配列を再利用する」のは賛成です。これは、読みやすさと潜在的なパフォーマンスの妥協点であるため、検討する価値があります。宣言された引数を渡しprivate static final MyClass[] EMPTY_MY_CLASS_ARRAY = new MyClass[0]ても、返される配列がリフレクションによって構築されるのを防ぐことできませんが、毎回追加の配列が構築されるのを防ぐことできます。
Michael Scheper 2013年

Machaelは正解です。長さゼロの配列を使用する場合、回避策はありません。(T [])java.lang.reflect.Array.newInstance(a.getClass()。getComponentType()、size); サイズが> = actualSize(JDK7)の場合、これは不必要です
アレックス

「この場合、最新のJVMはリフレクティブアレイの構築を最適化する」と引用していただければ、この回答に喜んで賛成いたします。
Tom Panning、2013年

ここで学んでいます。代わりに私が使用する場合:MyClass[] arr = myList.stream().toArray(MyClass[]::new);同期された同時並行のコレクションに役立つか、害を及ぼすか?なぜ?お願いします。
Pimp Trizkit

3

toArrayは、渡された配列が適切なサイズ(つまり、リストの要素に適合するのに十分な大きさ)であることを確認し、そうであればそれを使用します。したがって、配列のサイズが必要なサイズよりも小さい場合、新しい配列が再帰的に作成されます。

あなたの場合、サイズがゼロの配列は不変なので、静的な最終変数に安全に昇格できます。これにより、コードが少しきれいになり、呼び出しごとに配列が作成されなくなります。とにかく、メソッド内に新しい配列が作成されるため、読みやすさの最適化になります。

おそらく、より速いバージョンは正しいサイズの配列を渡すことですが、このコードがパフォーマンスのボトルネックであることを証明できない限り、他に証明されるまで実行時のパフォーマンスよりも読みやすさを優先してください。


2

最初のケースはより効率的です。

2番目のケースでは、

MyClass[] arr = myList.toArray(new MyClass[0]);

ランタイムは実際に空の配列(サイズがゼロ)を作成し、次にtoArrayメソッド内で実際のデータに合わせて別の配列を作成します。この作成は、次のコード(jdk1.5.0_10から取得)を使用したリフレクションを使用して行われます。

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.
    newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

最初の形式を使用することにより、2番目の配列の作成を回避し、リフレクションコードも回避します。


toArray()はリフレクションを使用しません。少なくとも、「キャスト」をリフレクションに数えない限り、;-)
Georgi

toArray(T [])は行います。適切なタイプの配列を作成する必要があります。最新のJVMは、この種のリフレクションを最適化して、非リフレクトバージョンとほぼ同じ速度にします。
トム・ホーティン-2008年

リフレクションを使っていると思います。JDK 1.5.0_10は確かに機能します。リフレクションは、コンパイル時にわからない型の配列を作成する唯一の方法です。
Panagiotis Korros 2008年

次に、彼女のソースコード例の1つ(上記のものまたは私のもの)は古くなっています。悲しいことに、私のサブバージョン番号が見つかりませんでした。
Georgi

1
Georgi、あなたのコードはJDK 1.6のものであり、Arrays.copyToメソッドの実装を見ると、実装がリフレクションを使用していることがわかります。
Panagiotis Korros 2008年

-1

2つ目はわずかに読み取り可能ですが、あまり改善されていないので、その価値はありません。最初の方法の方が高速で、実行時に不利になることはないので、それを使用しています。しかし、タイプする方が速いので、2番目の方法で記述します。次に、私のIDEが警告としてフラグを立て、修正を提案します。単一のキーストロークで、コードを2番目のタイプから最初のタイプに変換します。


-2

正しいサイズの配列で 'toArray'を使用すると、代わりに最初にゼロサイズの配列が作成され、次に正しいサイズの配列が作成されるため、パフォーマンスが向上します。しかし、あなたが言うように、違いは無視できる可能性が高いです。

また、javacコンパイラは最適化を実行しないことに注意してください。最近では、すべての最適化は実行時にJIT / HotSpotコンパイラーによって実行されます。JVMの「toArray」に関する最適化については知りません。

あなたの質問への答えは、主にスタイルの問題ですが、一貫性を保つために、(文書化されているかどうかにかかわらず)準拠するコーディング標準の一部を形成する必要があります。


OTOH、標準が長さ0の配列を使用する場合、逸脱するケースはパフォーマンスが問題であることを意味します。
Michael Scheper 2013年

-5

整数のサンプルコード:

Integer[] arr = myList.toArray(new integer[0]);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.