18

この質問はStackOverflowで何度も聞かれましたが、パフォーマンスに基づくものはありませんでした。

効果的なJavaの本では、

ループまたは頻繁に呼び出されるメソッドで発生する場合 String s = new String("stringette");、何百万ものStringインスタンスが不必要に作成される可能性があります。

改善されたバージョンは次のとおりです。 String s = "stringette";このバージョンは、実行されるたびに新しいインスタンスを作成するのではなく、単一のStringインスタンスを使用します。

そこで、両方を試してみたところ、パフォーマンスが大幅に向上していることがわかりました。

for (int j = 0; j < 1000; j++) {
    String s = new String("hello World");
}

399372ナノ秒かかります。

for (int j = 0; j < 1000; j++) {
    String s = "hello World";
}

23000ナノ秒かかります。

なぜこれほどパフォーマンスが向上するのですか?内部でコンパイラの最適化が行われていますか?

4

4 に答える 4

41

前者の場合、新しいオブジェクトが各反復で作成され、後者の場合、それは常に同じオブジェクトであり、文字列定数プールから取得されます。

Javaでは、次のことを行います。

String bla = new String("xpto");

新しいStringオブジェクトの作成を強制します。これには、時間とメモリが必要です。

一方、あなたがするとき:

String muchMuchFaster = "xpto"; //String literal!

文字列は最初にのみ作成され(新しいオブジェクト)、定数プールにキャッシュされるため、文字列String形式で参照するたびに、まったく同じオブジェクトが取得されます。これは驚くべきことです。速い。

ここで、質問するかもしれません...コード内の2つの異なるポイントが同じリテラルを取得して変更した場合、問題が発生することはありませんか?!

いいえ、Javaの文字列は、ご存知かもしれませんが、不変であるためです。したがって、文字列を変更する操作はすべて新しい文字列を返し、同じリテラルへの他の参照は途中で幸せになります。

これは不変のデータ構造の利点の1つですが、それはまったく別の問題であり、このテーマについて2、3ページを書きます。

編集

明確にするために、定数プールは文字列型に限定されていません。詳細については、ここで読むことができます。または、Java定数プールをグーグルで検索する場合もあります。

http://docs.oracle.com/javase/specs/jvms/se7/jvms7.pdf

また、ポイントを家に帰すためにできる小さなテスト:

String a = new String("xpto");
String b = new String("xpto");
String c = "xpto";
String d = "xpto";

System.out.println(a == b);
System.out.println(a == c);
System.out.println(c == d);

これらすべてで、おそらくこれらのSysoutsの結果を理解することができます。

false
false
true

cdは同じオブジェクトであるため、比較==は当てはまります。

于 2013-02-07T18:09:28.613 に答える
4

実際、パフォーマンスの違いははるかに大きくなっています。HotSpotを使用すると、ループ全体を簡単にコンパイルできます。

for (int j = 0; j < 1000; j++)
{String s="hello World";}

存在しないため、ランタイムは0になります。ただし、これはJITコンパイラーが起動した後にのみ発生します。これがウォームアップの目的であり、JVMで何かをマイクロベンチマークする場合の必須の手順です。

これは私が実行したコードです:

public static void timeLiteral() {
  for (int j = 0; j < 1_000_000_000; j++)
  {String s="hello World";}
}
public static void main(String... args) {
  for (int i = 0; i < 10; i++) {
    final long start = System.nanoTime();
    timeLiteral();
    System.out.println((System.nanoTime() - start) / 1000);
  }
}

そしてこれは典型的な出力です:

1412
38
25
1
1
0
0
1
0
1

JITがすぐに発効するのを見ることができます。

私は1000回繰り返すのではなく、内部メソッドで10億回繰り返すことに注意してください。

于 2013-02-07T18:27:04.740 に答える
1

すでに回答されているように、2番目は文字列プールからインスタンスを取得します(文字列は不変であることを忘れないでください)。

さらに、実行時に文字列の定数値がわからない場合に備えて、新しいString()をプールに入れることができるintern()メソッドを確認する必要があります。例:

String s = stringVar.intern();

また

new String(stringVar).intern();

さらに事実を追加します。Stringオブジェクトに加えて、プール(ハッシュコード)にさらに多くの情報が存在することを知っておく必要があります。これにより、関連するデータ構造内のStringによる高速hashMap検索が可能になります(毎回ハッシュコードを再作成する代わりに)

于 2013-02-07T18:14:05.557 に答える
0

JVMは、リテラルである一意のStringオブジェクトへの参照のプールを維持します。新しいStringの例では、リテラルをそれぞれのインスタンスでラップしています。

http://www.precisejava.com/javaperf/j2se/StringAndStringBuffer.htmを参照してください

于 2013-02-07T18:15:16.713 に答える