この記事を読んだ後、私はStringTableを理解しました

一生懸命勉強し、毎日改善する

この記事は私のGithubリポジトリに含まれていますDayDayUP:github.com/RobodLee/DayDayUP、Starへようこそ。その他の記事については、ディレクトリナビゲーションにアクセスしてください。

序文

StringはJavaで最も使用されるクラスである必要があります。Stringを使用しないJavaプログラムはほとんどありません。Javaでオブジェクトを作成することは非常にパフォーマンスを消費することであり、同じStringオブジェクトを使用することが多いため、これらの同じオブジェクトを作成してもパフォーマンスが無駄になることはありません。したがって、StringTableには特別な存在があります。StringTableは文字列定数プールと呼ばれ、文字列定数を格納するために使用されます。これにより、同じ文字列オブジェクトを使用するときに、オブジェクトを再作成せずにStringTableから直接取得できます。では、StringTableの特徴は何ですか?次に、StringTableを詳しく見てみましょう。

文字列のいくつかの機能

文字列の不変性

StringTableについて説明する前に、Stringの不変性について説明する必要があります。これは、StringTableの実現は、Stringが不変である場合にのみ可能であるためです。文字列を定義するとき:

String s = "hello";

このとき、「hello」はStringTableに格納され、変数sは参照であり、sはStringTableの「hello」を指します。

sの値を変更するときは、「helloworld」に変更してください。

String s = "hello";
s = "hello world";

このとき、sが指す「hello」の値を「helloworld」に変更するのではなく、新しい文字列を指します。

コンテンツを変更する代わりに新しい文字列を指していることを確認する方法は、ハッシュ値を出力して確認できます。

        String s = "hello";
        System.out.println(System.identityHashCode(s));
        s = "hello world";
        System.out.println(s.hashCode());
        s = "hello";
        System.out.println(System.identityHashCode(s));

ご覧のとおり、1回目と3回目のハッシュ値は同じで、2回目のハッシュ値は他の2つとは異なり、Stringの値を変更するのではなく、実際に新しいオブジェクトを指していることを示しています。

では、** Stringはどのようにして不変性を実現するのでしょうか?** Stringクラスのソースコードを見てみましょう。

ソースコードから、最初にStringクラスがfinalであり、継承できないことを示し、その不変の特性がサブクラスによって変更されないことがわかります。次に、Stringの最下層は、実際にはfinalによって変更された配列であり、この値を示します。値を決定した後は、新しい配列を指すことはできません。ここで、最終的に変更された配列は新しい配列を指すことはできませんが、配列の値は変更できることを明確にする必要があります。

変更できるのに、なぜStringは不変なのですか?Stringクラスは配列の値を変更するためのメソッドを提供しないため、Stringの不変性は、最終的なものではなく、その基礎となる実装によるものです。

では、なぜStringを不変に設計する必要があるのでしょうか。**セキュリティ上の理由によると思います。プログラム内に同じStringオブジェクトを同時に参照する場所が複数あると想像してみてください。ただし、Stringの内容を1か所で変更したい場合があります。すべての文字列の内容が変更される変数。これがパスワードの送信などの重要なシナリオである場合、大きな問題は発生しませんか?したがって、Stringは不変になるように設計されています。

文字列の連結

文字列の不変性について話した後、文字列のスプライシングの問題について話しましょう。次のプログラムを見てください。

public static void main(String[] args) {
    String a = "hello";
    String b = " world!";
    String c = a+b;
}

とてもシンプルなプログラムですが、どのように実装されているか知っていますか?このコードに対応するバイトコード命令を見てみましょう。

これらのバイトコード命令の意味を1行ずつ説明することはしません。赤でマークされた数行のコードに焦点を当てましょう。前のバイトコード命令を理解していなくても、以下のコメントを読むことができます。ご覧のとおり、文字列スプライシングは実際にはStringBuilderのappend()メソッドを呼び出してから、toString()メソッドを呼び出して新しい文字列を返します。

StringTableの説明

文字列はいつStringTableに入れられますか

最初にStringTableを簡単に紹介しましょう。その基礎となるデータ構造はHashTableであり、各要素は、配列+単一リンクリストの実装を使用しキー値構造です。

次のコードを見てください。

public static void main(String[] args) {
    
    
 -> String a = "hello";
    String b = " world!";
    String c = "hello world!";
}

クラスがロードされた後、文字列 "hello"はシンボルとしてランタイム定数プールにロードされ、まだ文字列オブジェクトにはなりません。これは、Javaの文字列がプログラムである遅延ロードメカニズムを使用しているためです。特定の行まで実行するときにロードします。たとえば、プログラムが矢印で示された行まで実行されると、「hello」はシンボルから文字列オブジェクトに変わり、次にStringTableに移動して同じ文字列オブジェクトがあるかどうかを確認し、存在する場合は、対応するアドレスをに返します。変数aがない場合は、StringTableに「hello」を入力してから、変数aにアドレスを指定します。これが当てはまるかどうかを見てみましょう:

        String s1 = "hello world";
        String s2 = "hello world";
        String s3 = "hello world";
        String s4 = "hello world";
        System.out.println(System.identityHashCode(s1));
        System.out.println(System.identityHashCode(s2));
        System.out.println(System.identityHashCode(s3));
        System.out.println(System.identityHashCode(s4));

4つの文字列オブジェクトのハッシュ値が同じであることがわかります。これは、同じオブジェクトがStringTableにすでに存在する場合、新しいオブジェクトを指すのではなく、同じオブジェクトを指すことを示しています。

new String()のときに何をしましたか

new String()を使用して文字列オブジェクトを作成する場合、String a = "hello"を直接書き込むことと同じではありません。前者はヒープメモリに保存され、後者はStringTableに保存されます。

実際、StringTableもヒープ内にあります。後で詳しく説明します。最初に上記のステートメントを確認しましょう。

        String a = "hello";
        String b = new String("hello");
        System.out.println(a == b);

実行結果を見てください:

結果は明らかに誤りであり、2つが実際に同じオブジェクトではないことを示しています。さらに、前述のように、文字列定数を指すと、最初にStringTableから検索し、見つかったときに見つかった文字列を直接返しますが、new String()の場合はそうではありません。新しい文字列ごとに、ヒープ内に新しい文字列が作成されます。オブジェクトは、同じコンテンツであっても、たとえば、4つのStringオブジェクトを作成します。

String s1 = new String("hello world");
String s2 = new String("hello world");
String s3 = new String("hello world");
String s4 = new String("hello world");

現時点では、ヒープ内に4つのStringオブジェクトがあります。

ハッシュをもう一度印刷して、4つのオブジェクトがあるかどうかを確認しましょう。

System.out.println(System.identityHashCode(s1));
System.out.println(System.identityHashCode(s2));
System.out.println(System.identityHashCode(s3));
System.out.println(System.identityHashCode(s4));

結果から、確かに4つの異なるオブジェクトがあることがわかります。

インターン方式とは

まず、コードの一部を見てみましょう。

String s1 = new String("hello world");
String s2 = "hello world";
String s3 = s1.intern();

System.out.println(s1 == s2);
System.out.println(s2 == s3);

結果を分析できるかどうか見てみましょう。すでに結果を知っている場合は、インターンメソッドを習得したことを意味します。わからない場合は、以下の説明を参照してください。

結果は偽であり、真です。インターンメソッドは何をしますか?

インターンメソッドの機能は、文字列をStringTableに入れようとすることです。文字列が存在しない場合は、StringTableに入れて、StringTableにアドレスを返します。存在する場合は、StringTableに直接アドレスを返します。これは、jdk1.8バージョンのinternメソッドの機能です。jdk1.6バージョンにはいくつかの違いがあります。1.6では、internは文字列オブジェクトをStringTableに配置しようとします。存在する場合は配置されません。存在しない場合は、オブジェクトをコピーします。 、それをStringTableに入れ、StringTableでオブジェクトを返します。ただし、ここではバージョン1.6については説明しません。

上記のコードを説明します。最初にヒープ内に「helloworld」文字列オブジェクトを作成し、s1がこのヒープ内のオブジェクトを指します。次に、StringTableに値「helloworld」を持つ文字列定数オブジェクトを作成します。 s2はこのStringTable内のオブジェクトを指します。最後に、s1が指すヒープ内のオブジェクトをStringTableに入れようとし、それがすでに存在することを確認して、StringTable内の文字列オブジェクトのアドレスをs3に返します。したがって、s1とs2は同じオブジェクトを指し、s2とs3は同じオブジェクトです。下の写真のように:

コードを少し変更するとどうなりますか?

String s1 = new String("hello world").intern();
String s2 = "hello world";

System.out.println(s1 == s2);

現時点では、結果はtrueです。分析してみましょう。最初にnewString()を使用してヒープ内に文字列オブジェクトを作成し、次にそのintern()メソッドを呼び出したため、StringTableで同じ文字列を検索したところ、文字列がないことがわかりました。それをStringTableに入れ、StringTable内のオブジェクトのアドレスをs1に指定します。2番目の行になると、new String()が使用されないため、StringTableから直接検索して見つけます。アドレスはs2に与えられるため、s1とs2は同じオブジェクトを指します。

StringTableの場所

前述のように、StringTableはヒープ内にあるので、確認してみましょう。検証方法は非常に単純です。メモリオーバーフローに多数の文字列を配置し、メモリのどの部分がオーバーフローするかを確認することで、StringTableがどこにあるかを確認できます。

ArrayList list = new ArrayList();
String str = "hello";
for(int i = 0;i < Integer.MAX_VALUE;i++) {
    
    
    String s = str + i;
    str = s;
    list.add(s.intern());
}

最初にinternメソッドを呼び出して文字列をStringTableに配置し、次にArrayListを使用して文字列を格納します。この方法では、各文字列が強く引用され、ゴミ収集されないため、ゴミ収集を回避することが目的です。リサイクルした後、期待する結果が得られません。結果を見てください:

明らかに、ヒープメモリでメモリオーバーフローが発生したため、判断できますStringTable是存放在堆中的ただし、これはバージョン1.7からのものであり、1.7より前の永続世代で保存されていました。

StringTableのガベージコレクション

ガベージコレクションについては前述したので、StringTableでガベージコレクションが発生するかどうかを確認しましょう。上記のコードはまだありますが、わずかに変更されています。

String str = "hello";
for(int i = 0;i < 10000;i++) {
    
    
    String s = str + i;
    s.intern();
}

文字列をArrayListに入れる必要はありません。または、メモリオーバーフローが発生した場合でも、ガベージコレクションは行われません。ガベージコレクションプロセスを確認するために、ヒープサイズを指定せずにいくつかの仮想マシンパラメータを追加します。

プログラムを実行して、印刷状況を確認します。

ヒープメモリが十分に大きいため、ガベージコレクションはありません。ヒープメモリを1mに小さく設定しましょう。

-Xmx1m

プログラムをもう一度実行してみましょう。

今回はヒープメモリ不足のため、複数のガベージコレクションが発生しました。したがって、StringTableはメモリ不足によるガベージコレクションも引き起こします

StringTableの低レベルの実装とパフォーマンスの調整

パフォーマンスチューニングを紹介する前に、StringTableの基盤となる実装について説明する必要があります。前述のように、基盤となるStringTableはHashTableです。HashTableはどのように見えますか?実際、これは配列+リンクリストであり、各要素はキー値です。要素が格納されると、そのキーがハッシュ関数を介して計算され、配列の添え字が取得され、対応する位置に格納されます。

たとえば、キー値があり、ハッシュ関数の結果がキーによって計算された2である場合、値は配列の添え字2の位置に格納されます。しかし、ハッシュ関数を介して同じ結果を計算する別のキーがある場合、たとえば、それも2ですが、2の位置にはすでに値があります。この現象はハッシュ競合と呼ばれます。どうすれば解決できますか?リンクリスト方式はここで使用されます:

リンクリスト方式は、同じ添え字の要素をリンクリストの形でつなぎ合わせる方法です。配列の容量は小さいが要素が多い場合、ハッシュの衝突の可能性が高くなります。リンクされたリストの効率は配列の効率よりはるかに低く、ハッシュの衝突が多すぎるとパフォーマンスに影響することは誰もが知っています。したがって、ハッシュの衝突の可能性を減らすために、配列のサイズを適切に増やすことができます。配列の各セルは、StringTableではバケットと呼ばれます。バケットの数を増やしてパフォーマンスを向上させることができます。デフォルトの数は60013です。比較を見てみましょう。

long startTime = System.nanoTime();
String str = "hello";
for(int i = 0;i < 500000;i++) {
    
    
    String s = str + i;
    s.intern();
}
long endTime = System.nanoTime();
System.out.println("花费的时间为:"+(endTime-startTime)/1000000 + "毫秒");

まず、仮想マシンのパラメータで小さいバケットを指定します。2000を作成しましょう。

 -XX:StringTableSize=2000

それを実行します:

合計1.2秒かかりました。バケットの数を少し増やして、20000にしましょう。

 -XX:StringTableSize=20000

それを実行します:

ご覧のとおり、今回は0.19秒しかかからず、パフォーマンスが大幅に向上しました。これは、StringTableを実際に最適化できることを示しています。ここでは、パフォーマンスを向上させる方法を1つだけ紹介します。スペースが限られているため、これ以上は説明しません。今後、StringTableのパフォーマンスの最適化について具体的に説明する記事を書く可能性があります。

最後に書く

これで記事は終わりです。内容が抜けている場合がありますので、どなたでもご覧いただけます。結局、レベルに限りがあります。私の記事があなたに役立つと思うなら、いいね、転送、ブックマーク、そしてフォローすることを忘れないでください!

WeChatパブリックアカウント

おすすめ

転載: blog.csdn.net/weixin_43461520/article/details/106932922