Javaでの文字列インターニングとは何ですか、いつ使用する必要がありますか、またその理由は何ですか?
8 に答える
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スペースが不足するリスクがあります。
-差出人:http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html
JDK 7(つまり、HotSpot)から、何かが変更されました。
JDK 7では、インターンされた文字列はJavaヒープの永続的な世代では割り当てられなくなりましたが、代わりに、アプリケーションによって作成された他のオブジェクトとともに、Javaヒープの主要部分(若い世代と古い世代として知られています)に割り当てられます。 。この変更により、メインのJavaヒープに存在するデータが増え、永続生成のデータが少なくなるため、ヒープサイズの調整が必要になる場合があります。ほとんどのアプリケーションでは、この変更によりヒープ使用量に比較的小さな違いしか見られませんが、多くのクラスをロードしたり、String.intern()メソッドを多用する大規模なアプリケーションでは、より大きな違いが見られます。
更新:インターンされた文字列は、Java7以降のメインヒープに格納されます。http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes
なぜあなたが平等になるのかなど、いくつかの「キャッチーなインタビュー」の質問があります!以下のコードを実行した場合。
String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");
文字列を比較する場合は、を使用する必要がありますequals()
。上記は、コンパイラによってtestString
すでにインターンされているため、equalsを出力します。前の回答に示されているように、インターンメソッドを使用して自分で文字列をインターンすることができます。
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インスタンスのinternメソッドが呼び出されます。
バイトコード
いくつかのOpenJDK7バイトコードを逆コンパイルして、インターンの動作を確認しましょう。
逆コンパイルした場合:
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
引数として)35
:a
とc
は通常のオブジェクトとして比較されますif_acmpne
定数文字列の表現は、バイトコードでは非常に魔法のようです。
- 通常のオブジェクト(例)とは異なり、専用のCONSTANT_String_info構造があります。
new String
- 構造体は、データを含むCONSTANT_Utf8_info構造体を指します。文字列を表すために必要なデータはこれだけです。
上記のJVMSの引用では、ポイントされたUtf8が同じである場合は常に、同一のインスタンスがによってロードされると述べているようですldc
。
私はフィールドに対して同様のテストを行いました、そして:
static final String s = "abc"
ConstantValue属性を介して定数テーブルを指します- 非最終フィールドにはその属性はありませんが、それでも初期化できます
ldc
結論:文字列プールには直接バイトコードがサポートされており、メモリ表現は効率的です。
ボーナス:直接バイトコードをサポートしていない(つまり、アナログがない)整数プールと比較してください。CONSTANT_String_info
Java8以降のアップデート。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()
に役立つかです。
文字列はオブジェクトであり、Javaのすべてのオブジェクトは常にヒープスペースにのみ格納されるため、すべての文字列はヒープスペースに格納されます。ただし、Javaは、「文字列プール」と呼ばれるヒープスペースの特別な領域に、newキーワードを使用せずに作成された文字列を保持します。Javaは、newキーワードを使用して作成された文字列を通常のヒープスペースに保持します。
文字列プールの目的は、一意の文字列のセットを維持することです。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オブジェクトへの参照を取得します。ただし、str3とstr4は、2つのまったく異なるStringオブジェクトへの参照を取得します。そのため、str1 == str2はtrueを返しますが、str1==str3およびstr3 ==str4はfalseを返します。実際、new String( "hello");を実行するとプログラムの任意の場所で文字列"hello"が初めて使用される場合は、1つではなく2つのStringオブジェクトが作成されます。1つは引用符で囲まれた文字列を使用するために文字列プールに、もう1つは通常のヒープスペースに使用されるためです。新しいキーワードの使用の。
文字列プーリングは、同じ値を含む複数の文字列オブジェクトの作成を回避することにより、プログラムメモリを節約するJavaの方法です。Stringのinternメソッドを使用して、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
文字列インターンは、コンパイラによる最適化手法です。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)両方に同じリテラルが含まれているためtrueであり、生成されたコードには「Int32」を参照するオブジェクトが1つだけ含まれます。注1を参照してください。
2)両方の値の内容が同じであることがチェックされるためtrue。
3)str2とobjに同じリテラルがないため、FALSE。注2を参照してください。
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.
ヒープオブジェクト参照を使用して、対応するSCPオブジェクト参照が必要な場合は、intern()メソッドを使用する必要があります。
例:
class InternDemo
{
public static void main(String[] args)
{
String s1=new String("smith");
String s2=s1.intern();
String s3="smith";
System.out.println(s2==s3);//true
}
}