Java には文字列オブジェクトの作成形式が 2 つあり、1 つは String str = "droid"; などのリテラル形式で、もう 1 つは String str = new String( など、オブジェクトを構築する標準メソッドである new を使用する形式です。 " droid");、これら 2 つのメソッドは、コードを記述するときによく使用されます (特にリテラル メソッド)。ただし、実際には、これら 2 つの実装の間にはパフォーマンスとメモリ使用量にいくつかの違いがあります。これはすべて、文字列オブジェクトの繰り返し作成を減らすために、JVM が文字列定数プールまたは文字列リテラル プールと呼ばれる特別なメモリを維持しているためです。
動作原理
コード内で文字列オブジェクトがリテラルの形式で作成されると、JVM はまずそのリテラルをチェックし、文字列定数プール内に同じ内容の文字列オブジェクトへの参照がある場合はその参照を返し、そうでない場合はその参照を返します。新しい文字列が作成され、オブジェクトが作成され、この参照が文字列定数プールに入れられ、参照が返されます。
例を挙げてください
リテラル作成フォーム
次のようにコードをコピーします。
文字列 str1 = "ドロイド";
JVM はこのリテラルを検出します。ここでは、コンテンツが droid であるオブジェクトはないと考えます。 JVM は、文字列定数プールを通じて droid の内容を含む文字列オブジェクトの存在を見つけることができません。その場合、JVM は文字列オブジェクトを作成し、新しく作成されたオブジェクトの参照を文字列定数プールに入れて、その参照を変数 str1 。
次にこのようなコードがあった場合
次のようにコードをコピーします。
文字列 str2 = "ドロイド";
同様に、JVM はこのリテラルを検出する必要があります。JVM は文字列定数プールを検索し、「droid」の内容を持つ文字列オブジェクトが存在することを検出し、既存の文字列オブジェクトの参照を変数 str2 に返します。ここでは新しい文字列オブジェクトが再作成されないことに注意してください。
str1 と str2 が同じオブジェクトを指しているかどうかを確認するには、次のコードを使用できます。
次のようにコードをコピーします。
System.out.println(str1 == str2);
結果は真実です。
新しいものを使用して作成する
次のようにコードをコピーします。
String str3 = new String("droid");
new を使用して文字列オブジェクトを構築すると、文字列定数プール内に同じ内容を持つオブジェクトへの参照があるかどうかに関係なく、新しい文字列オブジェクトが作成されます。したがって、次のコードを使用してテストします。
次のようにコードをコピーします。
String str3 = new String("droid");
System.out.println(str1 == str3);
結果は予想どおり false で、2 つの変数が異なるオブジェクトを指していることを示しています。
インターン
上記の new を使用して作成された文字列オブジェクトの場合、このオブジェクトの参照を文字列定数プールに追加する場合は、インターン メソッドを使用できます。
intern を呼び出した後、まず文字列定数プールにオブジェクトへの参照があるかどうかを確認し、存在する場合はその参照を変数に返します。
次のようにコードをコピーします。
文字列 str4 = str3.intern();
System.out.println(str4 == str1);
出力結果は true です。
難しい質問
前提条件?
文字列定数プールの実装の前提条件は、Java の String オブジェクトが不変であることです。これにより、複数の変数が同じオブジェクトを安全に共有できます。 Java の String オブジェクトが可変であり、参照操作によってオブジェクトの値が変更される場合、他の変数も影響を受けることは明らかです。
参照またはオブジェクト
最も一般的な問題は、参照またはオブジェクトが文字列定数プールに格納されるかどうかです。文字列定数プールには、オブジェクトではなくオブジェクト参照が格納されます。 Java では、オブジェクトはヒープ メモリに作成されます。
更新の検証。受け取った多くのコメントでもこの問題について議論されています。私はそれを簡単に検証しました。 検証環境:
次のようにコードをコピーします。
22:18:54-androidyue~/Videos$ cat /etc/os-release
名前=フェドーラ
VERSION="17 (ビーフィーミラクル)"
ID=フェドラ
VERSION_ID=17
PRETTY_NAME="Fedora 17 (ビーフィーミラクル)"
ANSI_COLOR="0;34"
CPE_NAME="cpe:/o:fedoraプロジェクト:fedora:17"
22:19:04-androidyue~/Videos$ java -version
Javaバージョン「1.7.0_25」
OpenJDK ランタイム環境 (fedora-2.3.12.1.fc17-x86_64)
OpenJDK 64 ビット サーバー VM (ビルド 23.7-b01、混合モード)
検証アイデア: 次の Java プログラムは、サイズ 82M のビデオ ファイルを読み取り、文字列の形式でインターン操作を実行します。
次のようにコードをコピーします。
22:01:17-androidyue~/Videos$ ll -lh grep Why_to_learn.mp4 |
-rw-rw-r--. 1 androidyue androidyue 82M 2013 年 10 月 20 日 Why_to_learn.mp4
検証コード
次のようにコードをコピーします。
java.io.BufferedReaderをインポートします。
インポートjava.io.FileNotFoundException;
java.io.FileReaderをインポートします。
インポート java.io.IOException;
パブリック クラス TestMain {
private static String fileContent;
public static void main(String[] args) {
fileContent = readFileToString(args[0]);
if (null != fileContent) {
fileContent = fileContent.intern();
System.out.println("Not Null");
}
}
private static String readFileToString(String file) {
BufferedReader リーダー = null;
試す {
リーダー = 新しい BufferedReader(新しい FileReader(ファイル));
StringBuffer buff = new StringBuffer();
文字列行;
while ((line = Reader.readLine()) != null) {
buff.append(行);
}
buff.toString() を返します。
} catch (FileNotFoundException e) {
e.printStackTrace();
} キャッチ (IOException e) {
e.printStackTrace();
} ついに {
if (null != リーダー) {
試す {
Reader.close();
} キャッチ (IOException e) {
e.printStackTrace();
}
}
}
null を返します。
}
}
文字列定数プールはヒープメモリ上の永続世代に存在するため、Java8以前から適用可能です。永続世代を非常に小さな値に設定することで、これを検証しました。文字列オブジェクトが文字列定数プールに存在する場合、必然的に java.lang.OutOfMemoryError permgen space エラーがスローされます。
次のようにコードをコピーします。
java -XX:PermSize=6m TestMain ~/Videos/why_to_learn.mp4
実際、証明プログラムを実行しても OOM はスローされませんでした。実際、オブジェクトが格納されているか参照が格納されているかを十分に証明することはできません。
しかし、これは少なくとも、文字列の実際のコンテンツ オブジェクト char[] が文字列定数プールに格納されていないことを証明しています。この場合、文字列定数プールに文字列オブジェクトが格納されるか、文字列オブジェクトへの参照が格納されるかは、実際にはそれほど重要ではありません。しかし、個人的には、それでも参考として保存しておきたいと思っています。
メリットとデメリット
文字列定数プールの利点は、同じ内容を持つ文字列の作成を減らし、メモリ領域を節約できることです。
デメリットについてあえて言うとすれば、スペースと引き換えに CPU の計算時間が犠牲になることです。 CPU の計算時間は主に、文字列定数プール内に同じ内容のオブジェクトへの参照があるかどうかを確認するために使用されます。ただし、内部実装はHashTableなので計算コストは低いです。
GCリサイクル?
文字列定数プールには共有文字列オブジェクトへの参照が保持されているため、これらのオブジェクトはリサイクルできないということですか?
まず、質問にある共有オブジェクトは一般に比較的小さいです。私の知る限り、この問題は以前のバージョンにも存在していましたが、弱参照の導入により、この問題は現在では解消されているはずです。
この問題については、「インターンされた文字列: Java 用語集」の記事で詳しく学ぶことができます。
インターン利用?
インターンを使用するための前提条件は、それを本当に使用する必要があることを知っていることです。たとえば、ここには数百万のレコードがあり、そのレコード内の特定の値が米国カリフォルニア州であることがよくありますが、そのような文字列オブジェクトを何百万も作成する必要はなく、メモリ内に 1 つのコピーだけを保持することができます。できる。 intern についてさらに詳しく理解するには、「String#intern の詳細な分析」を参照してください。
例外は常に存在しますか?
次のコードは、いくつかの文字列オブジェクトを作成し、文字列定数プールにいくつかの参照を保存することをご存知ですか?
次のようにコードをコピーします。
文字列テスト = "a" + "b" + "c";
答えは、定数プールにはオブジェクトが 1 つだけ作成され、参照が 1 つだけ保存されるということです。 javap を使用して逆コンパイルして調べてみるとわかります。
次のようにコードをコピーします。
17:02 $ javap -c TestInternedPoolGC
「TestInternedPoolGC.java」からコンパイル
public class TestInternedPoolGC extends java.lang.Object{
public TestInternedPoolGC();
コード:
0: aload_0
1: invokespecial #1 //メソッド java/lang/Object."<init>":()V;
4: 戻る
public static void main(java.lang.String[]) は java.lang.Exception をスローします。
コード:
0: ldc #2 //文字列 abc;
2:astore_1
3: 戻る
コンパイル中に、これら 3 つのリテラルが 1 つに結合されたことがわかりましたか?これは実際には、冗長な文字列オブジェクトの作成を回避し、文字列のスプライシングの問題を引き起こさない最適化です。文字列のスプライシングについては、Java の詳細: 文字列のスプライシングを参照してください。