205

私がArrayListを持っていると仮定します

ArrayList<MyClass> myList;

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

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

以上

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

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

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

4

9 に答える 9

139

直感に反して、Hotspot 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]);
  }
}

同様の結果、完全な分析、およびディスカッションは、ブログ投稿Arrays of Wisdom of the Ancients で見つけることができます。要約すると、JVM および JIT コンパイラには、適切なサイズの新しい配列を低コストで作成および初期化できるようにするいくつかの最適化が含まれています。これらの最適化は、配列を自分で作成する場合は使用できません。

于 2015-04-04T09:05:26.537 に答える
122

Java 5のArrayListの時点で、配列が適切なサイズ(またはそれより大きい)である場合、配列はすでにいっぱいになっています。その結果

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つのオブジェクトのみが作成され、キャストを回避します(暗黙的ですが高価です)。

于 2008-10-06T12:57:28.773 に答える
17

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

于 2008-10-06T13:17:25.830 に答える
3

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

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

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

于 2008-10-06T12:46:40.103 に答える
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番目の配列の作成を回避し、リフレクションコードも回避します。

于 2008-10-06T12:44:46.393 に答える
-1

2 番目のものはわずかに読みやすいですが、あまり改善されていないため、それだけの価値はありません。最初の方法は高速で、実行時に欠点がないため、私はそれを使用します。しかし、タイプするのが速いので、2 番目の方法で書きます。その後、私の IDE は警告としてフラグを立て、修正を提案します。1 回のキーストロークで、コードを 2 番目のタイプから最初のタイプに変換します。

于 2013-08-20T00:36:42.027 に答える
-2

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

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

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

于 2008-10-06T12:43:58.847 に答える
-4

integer のサンプル コード:

Integer[] arr = myList.toArray(new integer[0]);
于 2013-08-03T10:21:47.523 に答える