についてのJavadoc String.intern()
は詳細を提供していません。(簡単に言うと、文字列の正規表現を返し、インターンされた文字列をを使用して比較できるようにします==
)
- この関数をいつ使用するの
String.equals()
ですか? - Javadocに記載されていない副作用、つまりJITコンパイラによる多かれ少なかれ最適化はありますか?
- のさらなる使用はあり
String.intern()
ますか?
についてのJavadoc String.intern()
は詳細を提供していません。(簡単に言うと、文字列の正規表現を返し、インターンされた文字列をを使用して比較できるようにします==
)
String.equals()
ですか?String.intern()
ますか?回答:
String.equals()を優先してこの関数をいつ使用しますか
参照によって文字列を比較できるため、速度が必要な場合(==は等号より高速です)
Javadocに記載されていない副作用はありますか?
主な欠点は、比較する文字列をすべて実際にintern()することを忘れないようにする必要があることです。すべての文字列をintern()することを忘れるのは簡単です。そうすれば、混乱を招くほど不正確な結果を得る可能性があります。また、万が一のために、内部化される文字列に依存していることを非常に明確に文書化してください。
文字列を内部化することを決定した場合の2番目の欠点は、intern()メソッドが比較的高価になることです。一意の文字列のプールを管理する必要があるので、(文字列が既に内部化されている場合でも)かなりの作業が行われます。したがって、コード設計では注意してください。たとえば、入力時に適切なすべての文字列をintern()して、もう心配する必要がないようにしてください。
(JGuruから)
3番目の欠点(Java 7以下のみ):インターンされた文字列は、通常は非常に小さいPermGenスペースに存在します。十分な空きヒープ領域があるOutOfMemoryErrorが発生する可能性があります。
(Michael Borgwardtから)
if (s1.equals(s2))
、if (i1 == i2)
先頭の文字が同じ長い文字列が多数ない限り最小限です。(URL以外の)ほとんどの実際の使用では、文字列は最初の数文字内で異なります。とにかく、長いif-elseチェーンはコードのにおいです。列挙型とファンクターマップを使用してください。
これは(ほとんど)文字列の比較とは関係ありません。文字列インターニングは、アプリケーションに同じ内容の文字列が多数ある場合のメモリ節約を目的としています。String.intern()
アプリケーションを使用することにより、長期的には1つのインスタンスのみが存在し、副作用として、通常の文字列比較の代わりに高速参照等価比較を実行できます(ただし、これは通常、インターンだけを忘れることで簡単に破ることができるため、お勧めできません。単一インスタンス)。
str.intern()
にstr
あり"Hello"
ます。これは、後で再利用できるようになります。
String.intern()
間違いなく最新のJVMで収集されたガベージです。
次のGCアクティビティが原因で、メモリが不足することはありません。
// java -cp . -Xmx128m UserOfIntern
public class UserOfIntern {
public static void main(String[] args) {
Random random = new Random();
System.out.println(random.nextLong());
while (true) {
String s = String.valueOf(random.nextLong());
s = s.intern();
}
}
}
GCed以外のString.intern()の神話について(詳細はこちら)を参照してください。
OutOfMemoryException
-いや、ないコードの上、私の中の脳:javaturning記事を指しているこの記事で、これは... :-)を指しているjavaturning記事へのリンク
私は最近、Java 6、7、8でのString.intern()実装に関する記事を書きました: Java 6、7、8でのString.intern-文字列プーリング。
Javaでの文字列プーリングの現在の状況に関する十分な情報が含まれていることを願っています。
一言で言えば:
String.intern()
PermGenに入るので、Java 6ではString.intern()
のJava 7およびJava 8に:それはあなた自身のオブジェクトプールを転がすよりも4-5x少ないメモリを使用しています-XX:StringTableSize
(デフォルトはおそらく小さすぎます;素数を設定してください)==を使用した文字列の比較は、equals()を使用した場合よりもはるかに高速です
5時間速くなりますが、文字列比較は通常、アプリケーションの合計実行時間のごく一部にすぎないため、全体的なゲインはそれよりもはるかに小さく、最終的なゲインは数パーセントに希釈されます。
String.intern()は、ヒープから文字列を引き出し、PermGenに配置します
内部化された文字列は、別のストレージ領域に配置されます。永続的な生成は、クラス、メソッド、その他の内部JVMオブジェクトなどの非ユーザーオブジェクト用に予約されているJVMの領域です。この領域のサイズには制限があり、ヒープよりも貴重です。この領域がヒープよりも小さいと、すべてのスペースを使用してOutOfMemoryExceptionが発生する可能性が高くなります。
String.intern()文字列はガベージコレクションされます
JVMの新しいバージョンでは、オブジェクトから参照されていない場合、内部化された文字列もガベージコレクションされます。
上記の3つの点を念頭に置いて、多くの文字列比較を行う場合、String intern()はいくつかの状況でのみ役立つ可能性があることを差し引くことができますが、正確に何を知っているのかわからない場合は、内部文字列を使用しない方がよいでしょう。やっている ...
String.equals()を優先してこの関数をいつ使用しますか
彼らは異なることをするので、おそらく決してしません。
パフォーマンス上の理由から文字列をインターンして、参照が等しいかどうかを比較できるのは、しばらくの間文字列への参照を保持している場合にのみメリットがあります。ユーザー入力またはIOからの文字列はインターンされません。
つまり、アプリケーションでは、外部ソースから入力を受け取り、それをセマンティックな値(識別子など)を持つオブジェクトに処理しますが、そのオブジェクトには、生データと区別できないタイプがあり、プログラマーがどのようにすべきかについて異なるルールがありますこれを使って。
ほとんどの場合、UserId
インターンされ(スレッドセーフな汎用インターンメカニズムを作成するのは簡単です)、open enumのように機能するタイプを作成する方が、java.lang.String
型のタイプを作成する方が、たまたまユーザーIDである場合に参照セマンティクスでタイプます。
そうすることで、特定の文字列がインターンされているかどうかを混同することなく、必要な追加の動作をオープンな列挙型でカプセル化できます。
利点を認識していません。ある場合は、equals()自体が内部でintern()を使用すると思います(そうではありません)。
intern
非常に適切な理由がequals
あります。あなたが投稿したリンクは完全なブロックです。最後の段落intern
は、有効な使用シナリオがあることを認めています:重いテキスト処理(例:パーサー)。「あなたが何をしているのかわからない場合、[XYZ]は危険だ」と結論付けるのは非常に卑劣であり、身体的に痛いです。
ダニエル・ブリュックナーは完全に正しいです。文字列インターニングは、メモリ(ヒープ)を節約するためのものです。私たちのシステムには現在、特定のデータを保持するための巨大なハッシュマップがあります。システムのスケーリングに伴い、ハッシュマップはヒープをメモリ不足にするのに十分な大きさになります(テスト済み)。複製されたすべての文字列をハッシュマップ内のすべてのオブジェクトにインターンすることにより、ヒープ領域を大幅に節約できます。
また、Java 7では、抑留された文字列はPermGenに長くは存在しませんが、代わりにヒープが使用されます。したがって、そのサイズを気にする必要はありません。そうすれば、ガベージコレクションが行われます。
JDK 7では、インターンされた文字列は、Javaヒープの永続的な世代に割り当てられなくなりましたが、代わりに、アプリケーションによって作成された他のオブジェクトとともに、Javaヒープの主要部分(若い世代と古い世代と呼ばれます)に割り当てられます。この変更により、メインのJavaヒープに存在するデータが増え、永続的な世代に含まれるデータが少なくなるため、ヒープサイズを調整する必要がある場合があります。ほとんどのアプリケーションは、この変更によるヒープ使用量の比較的小さな違いしか表示しませんが、多くのクラスをロードしたり、String.intern()メソッドを頻繁に使用する大きなアプリケーションでは、より大きな違いが表示されます。
String
インスタンスによって使用されていることを示しました。それらのコンテンツを見ると、多くの重複があり、に切り替えることintern()
にしました。これにより、数百MBを節約できました。
Javadocに記載されていない副作用、つまりJITコンパイラによる多かれ少なかれ最適化はありますか?
JITレベルについては知りませんが、専用の文字列を使用して魔法のように効率的に実装されている文字列プールの直接バイトコードサポートがあります。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
方法に注意してください:
0
and 3
:同じldc #2
定数がロードされます(リテラル)12
:新しい文字列インスタンスが作成されます(#2
引数として)35
:a
とc
通常のオブジェクトとして比較されますif_acmpne
定数文字列の表現は、バイトコードでは非常に魅力的です。
new String
上記のJVMSの引用は、Utf8が同じである場合は常に、によって同一のインスタンスがロードされると述べているようですldc
。
私はフィールドに対して同様のテストを行いました、そして:
static final String s = "abc"
ConstantValue属性を介して定数テーブルを指しますldc
ボーナス:それを、バイトコードを直接サポートしていない(アナログがない)Integerプールと比較してくださいCONSTANT_String_info
。
subString()
ソースストリングと比較して結果が小さく、オブジェクトの寿命が長い場合、を使用すると、一種のメモリリークが発生する可能性があります。
通常の解決策は使用new String( s.subString(...))
することですが、潜在的/可能性の高い結果を格納するクラスがsubString(...)
あり、呼び出し元を制御できない場合はintern()
、コンストラクターに渡されるString引数のを格納することを検討できます。これにより、潜在的な大きなバッファが解放されます。
文字列インターンは、equals()
メソッドが頻繁に呼び出されている場合に便利ですequals()
。メソッドの最初のオブジェクトが同じかどうかをすばやく確認するためです。
if (this == anObject) {
return true;
}
これは通常Collection
、他のコードでも文字列の等価性チェックを実行する可能性がありますが、検索時に発生します。
ただし、インターンにはコストがかかります。コードのマイクロベンチマークを実行したところ、インターンプロセスによりランタイムが10倍に増加することがわかりました。
インターンを行うのに最適な場所は、通常、コード内の文字列が自動的にインターンされるため、コードの外部に格納されているキーを読み取る場合です。これは通常、最初のユーザーのペナルティを防ぐために、アプリケーションの初期化段階で発生します。
これを実行できるもう1つの場所は、キー検索に使用できるユーザー入力を処理するときです。これは通常、リクエストプロセッサで発生します。インターンされた文字列は渡されることに注意してください。
それを除けば、コードの残りの部分でインターンを行う意味はあまりありません。なぜなら、それは一般的に何の利益も与えないからです。
http://kohlerm.blogspot.co.uk/2009/01/is-javalangstringintern-really-evil.html
によると、以前にオブジェクトを比較するためにString.equals()
使用"=="
するアサートString
http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html
文字列の長さを比較してから、内容を比較します。
(ちなみに、販売カタログの製品コード文字列はすべて同じ長さになりがちです-BIC0417は自転車の安全ヘルメットです。TIG0003は生きている大人の男性の虎です-それらの1つを注文するには、おそらくあらゆる種類のライセンスが必要です。そして安全ヘルメットを同時に注文した方がいいかもしれません。)
つまり、文字intern()
列をバージョンで置き換えることでメリットを得られるように聞こえますが、equals()
プログラミングで「==」を使用せずに、安全性と読みやすさ、標準への準拠を得ることができます。そして、私が言おうとしていることのほとんどは、それが真実である場合、それが真実であることに依存しています。
しかしString.equals()
、使用する前に、他のオブジェクトではなく文字列を渡したことをテストします"=="
か?私は言う資格がありませんが、そうではないと思います。圧倒的にそのようなequals()
操作のほとんどは文字列から文字列になるため、ほとんどの場合テストに合格します。実際、「==」の内部で優先順位を付けるString.equals()
ことは、文字列を同じ実際のオブジェクトと頻繁に比較しているという確信を意味します。
次の行が「false」の結果を生成することに誰も驚かないことを願っています。
Integer i = 1;
System.out.println("1".equals(i));
しかし、2行目でに変更i
するi.toString()
と、もちろんtrue
です。
あなたがインターンからの利益を期待するかもしれない場所には、明らかに、Set
とがありMap
ます。インターンされた文字列のハッシュコードがキャッシュされていることを願っています...それは要件だと思います。そして、私が100万ドルを稼ぐことができるアイデアを与えただけではないことを願っています。:-)
メモリに関しては、文字列のボリュームが大きい場合、またはプログラムコードが使用するメモリを非常に小さくしたい場合は、これが重要な制限であることも明らかです。-distinct- Stringsのボリュームが非常に大きい場合は、専用のデータベースプログラムコードを使用してそれらを管理し、別のデータベースサーバーを使用することを検討する必要がある場合があります。同様に、小さなプログラム(同時に10000のインスタンスで実行する必要がある)を、文字列自体をまったく保存しないようにすることで改善できる場合。
新しい文字列を作成してすぐにそのintern()
代わりに破棄するのは無駄です。しかし、重複する文字列を保持する以外に明確な代替方法はありません。したがって、実際の実行コストは、インターンプールで文字列を検索し、ガベージコレクターが元の文字列を破棄できるようにすることです。そしてそれが文字列リテラルであれば、とにかくすでにインターンされています。
intern()
悪意のあるプログラムコードによって悪用されて、Stringとそのオブジェクト参照がすでにintern()
プールに存在するかどうか、したがってJavaセッションの他の場所に存在するかどうか(それが知られていない場合)を検出できるかどうか疑問に思っています。しかし、それは、プログラムコードが既に信頼できる方法で使用されている場合にのみ可能だと思います。それでも、ATM PIN番号を保存および記憶するためにプログラムに含めるサードパーティライブラリについて考慮する必要があります。
インターンを使用する本当の理由は上記ではありません。メモリ不足エラーが発生した後に使用できます。典型的なプログラムの多くの文字列は、他の大きな文字列のString.substring()です(100K xmlファイルからユーザー名を取り出すことを考えてください)。Javaの実装では、部分文字列は元の文字列への参照とその巨大な文字列のstart + endを保持します。(その背後にある考えは、同じ大きな文字列の再利用です)
1000個の短いファイルを保存するだけの1000個の大きなファイルの後、1000個のファイル全体をメモリに保持します。解決策:このシナリオでは、smallsubstring.intern()を使用するだけです
それに直面しましょう:主なユースケースシナリオは、データストリームを(入力ストリームを介して、またはJDBC ResultSetから)読み取り、無数の小さな文字列が全体にわたって繰り返される場合です。
以下は、文字列やその他の不変式を内部化するために使用するメカニズムの種類、および実装例を制御するための小さなトリックです。
/**
* Extends the notion of String.intern() to different mechanisms and
* different types. For example, an implementation can use an
* LRUCache<T,?>, or a WeakHashMap.
*/
public interface Internalizer<T> {
public T get(T obj);
}
public static class LRUInternalizer<T> implements Internalizer<T> {
private final LRUCache<T, T> cache;
public LRUInternalizer(int size) {
cache = new LRUCache<T, T>(size) {
private static final long serialVersionUID = 1L;
@Override
protected T retrieve(T key) {
return key;
}
};
}
@Override
public T get(T obj) {
return cache.get(obj);
}
}
public class PermGenInternalizer implements Internalizer<String> {
@Override
public String get(String obj) {
return obj.intern();
}
}
ストリームまたはResultSetからフィールドを読み取るときによく使用します。注:LRUCache
はに基づく単純なキャッシュですLinkedHashMap<K,V>
。retrieve()
すべてのキャッシュミスに対して、ユーザー指定のメソッドを自動的に呼び出します。
これを使用する方法はLRUInternalizer
、読み取り(または複数の読み取り)の前に作成し、それを使用して文字列と他の小さな不変オブジェクトを内部化してから解放することです。例えば:
Internalizer<String> internalizer = new LRUInternalizer(2048);
// ... get some object "input" that stream fields
for (String s : input.nextField()) {
s = internalizer.get(s);
// store s...
}
関連する名前にリンクする約36000のコードのコンテンツをキャッシュするために使用しています。多くのコードが同じ文字列を指しているため、キャッシュ内の文字列をインターンします。
キャッシュ内の文字列を処理することで、同じ文字列を指すコードが実際に同じメモリを指すようにして、RAMスペースを節約しています。
インターンされた文字列が実際にガベージコレクションされた場合、それは私にはまったく機能しません。これは基本的にインターンの目的を無効にします。私はキャッシュ内のすべての文字列への参照を保持しているので、鉱山はガベージコレクションされません。
文字列をインターンするコストは、単一のstringA.equals(B)比較で節約される時間よりもはるかに多くなります。同じパフォーマンスの文字列変数を繰り返し使用している場合にのみ(パフォーマンス上の理由から)使用してください。たとえば、文字列の安定したリストを定期的に反復して、同じ文字列フィールドをキーとするいくつかのマップを更新すると、かなりの節約になります。
コードの特定の部分を最適化するときは、文字列インターニングを使用してパフォーマンスを調整することをお勧めします。
また、Stringは不変であり、愚かな間違いをしないことも忘れないでください。
String a = SOME_RANDOM_VALUE
a.intern()
忘れずに
String a = SOME_RANDOM_VALUE.intern()
String.internの無制限の置き換え、ガベージコレクションも探している場合は、次の方法でうまくいきます。
private static WeakHashMap<String, WeakReference<String>> internStrings = new WeakHashMap<>();
public static String internalize(String k) {
synchronized (internStrings) {
WeakReference<String> weakReference = internStrings.get(k);
String v = weakReference != null ? weakReference.get() : null;
if (v == null) {
v = k;
internStrings.put(v, new WeakReference<String>(v));
}
return v;
}
}
もちろん、存在するさまざまな文字列の数を大まかに見積もることができる場合は、-XX:StringTableSize = highEnoughValueを指定してString.intern()を使用するだけです。