web-dev-qa-db-ja.com

Java文字列インターンとは何ですか?

JavaのString Interningとは何ですか?why

203
saplingPro

http://docs.Oracle.com/javase/7/docs/api/Java/lang/String.html#intern()

基本的に、一連の文字列に対してString.intern()を実行すると、同じコンテンツを持つすべての文字列が同じメモリを共有するようになります。したがって、「john」が1000回出現する名前のリストがある場合、インターンすることで、実際には1つの「john」にのみメモリが割り当てられます。

これは、プログラムのメモリ要件を削減するのに役立ちます。ただし、キャッシュは通常ヒープと比較してサイズが制限されている永続メモリプールのJVMによって維持されるため、重複する値があま​​り多くない場合はインターンを使用しないでください。


Intern()を使用した場合のメモリ制約の詳細

一方では、文字列の重複を内部化することで削除できるのは事実です。問題は、内部化された文字列が永続世代に送られることです。永続世代は、クラス、メソッド、その他の内部JVMオブジェクトなどの非ユーザーオブジェクト用に予約されているJVMの領域です。この領域のサイズは制限されており、通常はヒープよりもはるかに小さくなっています。 Stringでintern()を呼び出すと、ヒープから永続世代に移動する効果があり、PermGenスペースが不足する危険があります。

-From: http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html


JDK 7(HotSpotを意味します)から、何かが変わりました。

JDK 7では、インターンされた文字列はJavaヒープの永続的な世代には割り当てられなくなりましたが、代わりにJavaヒープのメイン部分(新旧とも呼ばれます)に割り当てられます世代)、アプリケーションによって作成された他のオブジェクトとともに。この変更により、メインJavaヒープにあるデータが増え、永続世代のデータが少なくなるため、ヒープサイズの調整が必要になる場合があります。ほとんどのアプリケーションでは、この変更によりヒープ使用量に比較的小さな違いしか見られませんが、多くのクラスをロードしたり、String.intern()メソッドを多用したりする大きなアプリケーションでは、より大きな違いが見られます。

-から Java SE 7の機能と拡張機能

更新:インターンされた文字列は、Java 7以降のメインヒープに格納されます。 http://www.Oracle.com/technetwork/Java/javase/jdk7-relnotes-418459.html#jdk7changes

206
Ashwinee K Jha

以下のコードを実行するとなぜequals!になるのかなど、「キャッチーなインタビュー」の質問があります。

String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");

文字列を比較する場合は、equals()を使用する必要があります。 testStringはすでにinternedであるため、上記はequalsを出力します。前の回答に示されているように、インターンメソッドを使用して文字列を自分でインターンできます。

60
maslan

JLS

JLS 7 3.10.5 はそれを定義し、実用的な例を示します:

さらに、文字列リテラルは常にStringクラスの同じインスタンスを参照します。これは、String.internメソッドを使用して一意のインスタンスを共有するために、文字列リテラル、またはより一般的には定数式(§15.28)の値である文字列が「インターン」されるためです。

例3.10.5-1。文字列リテラル

コンパイル単位(§7.3)で構成されるプログラム:

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

コンパイル単位:

package other;
public class Other { public static String hello = "Hello"; }

出力を生成します:

true true true true false true

JVMS

JVMS 7 5.1による は、専用のCONSTANT_String_info構造体を使用してインターンが魔法のように効率的に実装されることを示します(より一般的な表現を持つ他のほとんどのオブジェクトとは異なります):

文字列リテラルは、クラスStringのインスタンスへの参照であり、クラスまたはインターフェイスのバイナリ表現のCONSTANT_String_info構造(§4.4.3)から派生します。 CONSTANT_String_info構造体は、文字列リテラルを構成するUnicodeコードポイントのシーケンスを提供します。

Javaプログラミング言語では、同一の文字列リテラル(つまり、同じコードポイントのシーケンスを含むリテラル)がクラスStringの同じインスタンスを参照する必要があります(JLS§3.10.5)。さらに、文字列でString.internメソッドが呼び出された場合、結果は、その文字列がリテラルとして出現した場合に返される同じクラスインスタンスへの参照になります。したがって、次の式の値はtrueでなければなりません。

("a" + "b" + "c").intern() == "abc"

文字列リテラルを導出するために、Java仮想マシンは、CONSTANT_String_info構造体によって指定されたコードポイントのシーケンスを調べます。

  • メソッドString.internが、CONSTANT_String_info構造で指定されたものと同一のUnicodeコードポイントのシーケンスを含むクラスStringのインスタンスで以前に呼び出された場合、文字列リテラル派生の結果は、クラスStringの同じインスタンスへの参照になります。

  • それ以外の場合、CONSTANT_String_info構造体によって指定されたUnicodeコードポイントのシーケンスを含むクラスStringの新しいインスタンスが作成されます。そのクラスインスタンスへの参照は、文字列リテラルの派生の結果です。最後に、新しいStringインスタンスのinternメソッドが呼び出されます。

バイトコード

いくつかのOpenJDK 7バイトコードを逆コンパイルして、インターンの動作を確認しましょう。

逆コンパイルする場合:

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

定数プールにあります:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

およびmain

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class Java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method Java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field Java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field Java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field Java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method Java/io/PrintStream.println:(Z)V

方法に注意してください:

  • 0および3:同じldc #2定数がロードされます(リテラル)
  • 12:新しい文字列インスタンスが作成されます(引数として#2を使用)
  • 35acは、通常のオブジェクトとしてif_acmpneと比較されます

定数文字列の表現は、バイトコードでは非常に魔法です。

  • 通常のオブジェクトとは異なり、専用の CONSTANT_String_info 構造があります(例:new String
  • 構造体は、データを含む CONSTANT_Utf8_info構造体 を指します。これは、文字列を表すために必要な唯一のデータです。

上記のJVMSの引用では、指しているUtf8が同じ場合は常に、ldcによって同一のインスタンスがロードされると言われているようです。

フィールドについても同様のテストを行いました。

  • static final String s = "abc"ConstantValue Attribute を介して定数テーブルを指します
  • 非最終フィールドにはその属性はありませんが、ldcで初期化できます

結論:文字列プールの直接バイトコードサポートがあり、メモリ表現が効率的です。

ボーナス: 整数プール と比較してください。これは、直接バイトコードをサポートしていません(つまり、CONSTANT_String_infoアナログなし)。

Java 8またはプラスの更新。 Java 8では、PermGen(永続世代)スペースが削除され、メタスペースに置き換えられます。文字列プールメモリはJVMのヒープに移動されます。

Java 7と比較すると、ヒープ内の文字列プールサイズが増加します。したがって、内部化された文字列用のスペースは増えますが、アプリケーション全体のメモリは少なくなります。

もう1つ、Javaの2つのオブジェクト(の参照)を比較するとき、オブジェクトの参照の比較に「==」が使用され、オブジェクトの内容の比較に「equals」が使用されることが既にわかっています。

このコードを確認しましょう:

String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();

結果:

value1 == value2 ---> true

value1 == value3 ---> false

value1.equals(value3) ---> true

value1 == value3.intern() ---> true

そのため、「equals」を使用して2つのStringオブジェクトを比較する必要があります。そして、それがintern()の有用性です。

12
nguyentt

文字列インターンは、コンパイラーによる最適化手法です。 1つのコンパイルユニットに2つの同一の文字列リテラルがある場合、生成されるコードにより、アセンブリ内のそのリテラル(二重引用符で囲まれた文字)のすべてのインスタンスに対して1つの文字列オブジェクトのみが作成されます。

私はC#のバックグラウンドであるため、例を挙げて説明できます。

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;

次の比較の出力:

Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true    
Console.WriteLine(obj == str2); // false !?

注1:オブジェクトは参照によって比較されます。

Note2:typeof(int).Nameはリフレクションメソッドによって評価されるため、コンパイル時に評価されません。 ここでこれらの比較はコンパイル時に行われます。

結果の分析: 1)両方に同じリテラルが含まれているため、生成されるコードには「Int32」を参照するオブジェクトが1つしかないため、true。 注1を参照

2)同じである両方の値の内容がチェックされるため、true。

3)str2とobjに同じリテラルがないため、FALSE。 注2を参照してください。

2
Robin Gupta