Java Stringインターニングとは何ですか?


234

Javaでの文字列インターニングとは何ですか。いつ使用する必要がありますなぜですか。



2
もしString a = new String("abc"); String b = new String("abc"); 、その後a.intern() == b.intern()
Asanka Siriwardena

チェックアウト文字列のインターンの例:algs4.cs.princeton.edu/12oop/MutableString.java.html
Ronak Poriya

String.intern()依存していますかClassLoader、つまり、異なるクラスローダーが「異なる」を作成してString、異なるを引き起こしていinternますか?
AlikElzin-kilaka

1
@ AlikElzin-kilakaいいえ、クラスローダーは文字列のインターンとはまったく関係ありません。次に質問がある場合は、別の質問にコメントとして投稿するのではなく、新しい質問開いてください。
ホルガー

回答:


233

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

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

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


intern()を使用する場合のメモリ制約の詳細

一方では、文字列の重複を内部化することで削除できることは事実です。問題は、内部化された文字列が永続生成に送られることです。これは、クラス、メソッド、その他の内部JVMオブジェクトなどの非ユーザーオブジェクト用に予約されているJVMの領域です。この領域のサイズには制限があり、通常はヒープよりはるかに小さくなります。文字列で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


1
「しかし、キャッシュは通常、サイズが制限されている永続的なメモリプールでJVMによって維持されることに注意してください……」これについて説明できますか?わかりませんでした
saplingPro

2
「インターンされた」文字列は、JVMの特別なメモリ領域に格納されます。このメモリ領域は通常、固定サイズであり、他のデータが格納される通常のJavaヒープの一部ではありません。サイズが固定されているため、この永続的なメモリ領域がすべての文字列でいっぱいになり、醜い問題(クラスをロードできないなど)につながる可能性があります。
チェロ

@celloそう、それはキャッシングに似ていますか?
saplingPro 2012年

8
@grassPro:はい、それは一種のキャッシングであり、JVMによってネイティブに提供されるものです。注意として、Sun / Oracle JVMとJRockitの統合により、JVMエンジニアはJDK 8(openjdk.java.net/jeps/122)の永続メモリ領域を削除しようとするため、将来のサイズ制限。
チェロ

9
プログラマは、文字列のインターンがセキュリティに影響を与える可能性があることにも注意する必要があります。パスワードなどの機密性の高いテキストを文字列としてメモリに保持している場合、実際の文字列オブジェクトが長期間GCされていても、非常に長い間メモリに残っている可能性があります。悪意のある人が何らかの方法でメモリダンプにアクセスする場合、これは問題になります。この問題は、インターンを使用しなくても存在します(GCは最初は決定論的ではないため)、多少悪化します。デリケートなテキストのchar[]代わりに使用Stringし、不要になったらすぐにゼロにすることをお勧めします。
クリス

71

対等になる理由など、「キャッチーなインタビュー」の質問があります以下のコードを実行すると、

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

文字列を比較したい場合は、を使用してくださいequals()。上記はコンパイラによってtestStringすでにインターンされているので、equals を出力します。以前の回答で示したように、インターンメソッドを使用して自分で文字列をインターンできます。


5
あなたの例はトリッキーなので、equalsメソッドを使用しても同じプリントになります。new String()比較を追加して、違いをより明確に示すことができます。
giannis christofakis

@giannischristofakisですが、new String()を使用すると、==は失敗しませんか?Javaは自動的に新しい文字列も内部化しますか?
Deepak Selvakumar

@giannischristofakisもちろん、新しいString()を使用すると、==で失敗します。しかし、新しいString(...)。intern()は、==で失敗しません。これは、インターンが同じ文字列を返すためです。単純な仮定コンパイラーがリテラルで新しいString()。internを実行している
マスラン

42

JLS

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

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

例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インスタンスのインターンメソッドが呼び出されます。

バイトコード

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

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

  • 0and 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属性を介して定数テーブルを指します
  • 非最終フィールドにはその属性はありませんが、次のように初期化できます ldc

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

おまけ:それを、バイトコードを直接サポートしていない(アナログではない)Integerプールと比較してくださいCONSTANT_String_info


19

Java 8以降のアップデート。Java 8では、PermGen(Permanent Generation)スペースが削除され、メタスペースに置き換えられました。文字列プールのメモリは、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()に役立つかです。


2

文字列インターンは、コンパイラによる最適化手法です。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:オブジェクトは参照によって比較されます。

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

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

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

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


3
それよりも強いです。同じクラスローダーによって読み込まれたすべての文字列リテラルは、同じ文字列を参照します。JLSおよびJVM仕様を参照してください。
ローンの侯爵

1
@ user207421実際には、文字列リテラルがどのクラスローダーに属しているかは関係ありません。
Holger

1
Java interning() method basically makes sure that if String object is present in SCP, If yes then it returns that object and if not then creates that objects in SCP and return its references

for eg: String s1=new String("abc");
        String s2="abc";
        String s3="abc";

s1==s2// false, because 1 object of s1 is stored in heap and other in scp(but this objects doesn't have explicit reference) and s2 in scp
s2==s3// true

now if we do intern on s1
s1=s1.intern() 

//JVM checks if there is any string in the pool with value “abc” is present? Since there is a string object in the pool with value “abc”, its reference is returned.
Notice that we are calling s1 = s1.intern(), so the s1 is now referring to the string pool object having value abc”.
At this point, all the three string objects are referring to the same object in the string pool. Hence s1==s2 is returning true now.

0

OCP Java SE 11 Programmer Deshmukhの本から、次のようなInterningの最も簡単な説明がわかりました。ただし、Javaは、「キーワードプール」と呼ばれるヒープスペースの特別な領域に新しいキーワードを使用せずに作成された文字列を保持します。Javaは、新しいキーワードを使用して作成された文字列を通常のヒープ領域に保持します。

文字列プールの目的は、一意の文字列のセットを維持することです。newキーワードを使用せずに新しい文字列を作成すると、Javaは同じ文字列が文字列プールにすでに存在するかどうかを確認します。含まれている場合、Javaは同じStringオブジェクトへの参照を返します。含まれていない場合、Javaは文字列プールに新しいStringオブジェクトを作成し、その参照を返します。したがって、たとえば、以下のようにコード内で文字列 "hello"を2回使用すると、同じ文字列への参照が取得されます。次のコードに示すように、==演算子を使用して2つの異なる参照変数を比較することで、この理論を実際にテストできます。

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //prints true

String str3 = new String("hello");
String str4 = new String("hello");

System.out.println(str1 == str3); //prints false
System.out.println(str3 == str4); //prints false 

==演算子は、2つの参照が同じオブジェクトを指しているかどうかを単にチェックし、指している場合はtrueを返します。上記のコードでは、str2は以前に作成された同じStringオブジェクトへの参照を取得します。ただし、str3str4は2つのまったく異なるStringオブジェクトへの参照を取得します。そのため、str1 == str2はtrueを返しますが、str1 == str3およびstr3 == str4は falseを返します。実際、新しいString( "hello");を実行すると、 これがプログラムの任意の場所で初めて使用された場合、1つではなく2つのStringオブジェクトが作成されます。1つは引用符付き文字列の使用のために文字列プールに、もう1つは通常のヒープ領域に使用されます。新しいキーワードの使用の。

文字列プーリングは、同じ値を含む複数の文字列オブジェクトの作成を回避することによってプログラムメモリを節約するJavaの方法です。Stringのインターンメソッドを使用して、newキーワードを使用して作成された文字列の文字列プールから文字列を取得することが可能です。文字列オブジェクトの「内部」と呼ばれます。例えば、

String str1 = "hello";
String str2 = new String("hello");
String str3 = str2.intern(); //get an interned string obj

System.out.println(str1 == str2); //prints false
System.out.println(str1 == str3); //prints true
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.