【面接準備】文字列の詳細

みなさん、こんにちは。私はキャベツに支配されている豚です。

勉強が大好きで、眠れず、食べるのを忘れている人は、女の子のシックで落ち着いた、無関心なコーディングのハンサムな男の子に夢中です。

私のテキストが気に入ったら、「このキャベツを手放して、私を来させてください」という公開アカウントに従ってください。

文字列を本当に理解していますか

序文

One Dailyの第2号では、Stringに関するこれらのことを明確にしています。

本文を始める前に、このシリーズの執筆の目的について簡単に説明します。まず、私にとっては、知識の理解を深めるために、言語と理解を通して知識のポイントを繰り返します。面接のプロセスを達成するために。「自由に話す」ことができ、心をパニックにすることなく胃の在庫を保つことができます。2つ目は、1日に1本の爪で自分を促し、先延ばしに別れを告げることです(ただし、今は夕方11時に、旗を立てるだけではない場合は、顔を叩く必要があります)。第二に、読者の皆さんには、就職を考えている学生がたくさんいると思います。一緒に仕事をしたり、進歩したり、埋め合わせたりするのに遅すぎることはありません。私の言葉がお役に立てば幸いです。

記事自体の内容は、主にJavaインタビュー用です。毎日知識ポイントを取得し、時間をかけて蓄積していくことも大きなメリットだと思います。さて、私の足跡をたどって、Stringの背後にある珍しい側面を見てみましょう。

前書き

今回は、主に文字列定数プール、文字列スプライシング、intern()について説明し、JVMレベルで関連するインタビューの質問と組み合わせて、基本的な原則の詳細な分析を説明します。以前は、関連するコンテンツを暗記するだけでしたが、鈍い言葉は覚えにくいだけでなく、恥ずかしすぎて忘れることができませんでした。知識を学ぶときは、張無忌のようにQishangquanを学び、その本質を理解し、言葉が標準の答えとまったく同じであるかどうかを心配する必要はないと思います。この記事の内容も「忘れて」の状態で書かれます。

文字列の概要

エンコードの過程で、誰も弦を使わないのですが、弦についてどれだけ知っていますか?これは熟考する価値のある質問だと思います。この記事を読んだ後、Xiaobaiは大きな利益を得ると思います。なぜあなたは言うのですか?それは、私も初心者だからです。

文字列の最も重要な特性の1つは、不変性です。なんでそんなこと言うの?ソースコードをクリックすると、そのクラス構造が表示されます。
ここに画像の説明を挿入します

これはfinalで装飾されており、継承することはできません。実際に維持されるのは、文字列データを格納するために最終的に変更されたchar配列です。しかし、jdk 9以降では、byte []に​​なります。これはなぜですか?公式サイトの内容を見てみましょう。
ここに画像の説明を挿入します

大まかな変換とは、charを使用して2バイトを格納することを意味しますが、ほとんどの文字はラテン語であり、ラテン語は1バイトでしか格納できないため、スペースの半分が無駄になります。それで、文字列を格納するためにバイトに変更しましたが、2バイト前に占めていた文字はどうですか?中国語と同様に2文字を使用します。このドキュメントでは、エンコードフラグとマークを使用して、2文字を使用していることを示すことを提案しています。これにより、スペースを大幅に節約できます。

もちろん、対応するStringBuilderとStringBufferもそれに応じて変更されています。

文字列定数プール

メモリ割り当てに関しては、文字列は文字列定数プールと呼ばれる場所に配置されます。定数プールは、Javaシステムレベルによって提供されるキャッシュに似ています。必要に応じて最初にキャッシュがあるかどうかを確認し、作成する代わりに直接使用します。これにより、実行速度とメモリの節約が保証されます。

文字列定数プールの非常に重要な機能は、同じコンテンツの文字列を格納しないことです。

jdkバージョンの変更

また、jdk6とjdk6以降のバージョンでは、文字列定数プールの位置が異なることに注意してください。jdk6の後、文字列定数プールは元の永続世代(jdk 8がメタスペースになりました)領域からヒープに移動されましたこれは私たちの注意が必要な点です。それの異なる位置は、彼の最終的な保管のメカニズムが異なることを決定します。
ここに画像の説明を挿入します

jdk8では、文字列定数プールは引き続きヒープに配置されますが、永続世代はメタスペースになり、JVMによって割り当てられたメモリではなくローカルメモリに配置されます。

挿入方法

多くのことを言ってきましたが、文字列を文字列定数プールに入れる方法は2つあります。

  • 1つは、リテラルを使用することです。つまり、二重引用符を直接使用します。二重引用符を直接使用する文字列オブジェクトは、定数プールに直接格納されます例:String info = "ljlはハンサムな男の子です";
  • 2つ目は、Stringが提供するintern()メソッドを使用できます。これについては後で説明します。

このとき、誰かが新しいキーワードを使用してStringオブジェクトを作成するのはどうでしょうか。例:String info = new String( "ljl is a good man");

それでおしまい。

new String(“ ljl is a good man”);

まず、面接でよくある質問を取り上げます。

タイトル:新しいString( "ljl is a good man")はいくつのオブジェクトを作成しますか?

この質問を見るのは少し気が遠くなります、新しいものは新しいものではないので、簡単な質問がされます、インタビュアーはそれが弱い志ではないことを恐れています、あなたがそう思うなら、あなたは間違っています、誰もがまだこの質問をしますか?答えは2つです。なぜ2つあるのですか?最下層に深く入り込み、バイトコードファイルを見てみましょう。一目でわかります。
ここに画像の説明を挿入します

オブジェクトは次のとおりです。新しいキーワードがヒープスペースに作成されます。

別のオブジェクトは次のとおりです文字列定数プールで作成されたオブジェクト「ljlisagoodman」バイトコード命令:ldc。

このように記憶して、new String( "ljl is a good man");を2つの部分に分割できます。1つは括弧内の "ljl is a good man"で、もう1つはnew String()です。前者は使用法です。前に説明したように、リテラルメソッドは、引用符を直接使用して文字列を文字列定数プールに格納し、newはヒープ内に通常のオブジェクトを作成し、定数プールから文字列を取得して割り当てます。

しかし実際には、2つの異なるメソッドは同じ配列を維持します。これは理解する必要があるだけです。定数プールの「abc」とヒープの「abc」がリフレクションによって下部に同じ配列を格納していることを確認できます。 1。

@Test
public void test1() throws Exception{
    
    
    String s1 = "abc";
    String s2 = new String("abc");
    Field field = String.class.getDeclaredField("value");
    //将字段设置为可访问的
    field.setAccessible(true);
    char[] arr = (char[]) field.get(s1);
    arr[0] = 'l';
    System.out.println(s1);//输出是lbc
    System.out.println(s2);//输出也是lbc
}

ブレーンストーミングを回避するために、ヒープ内にabcの値を持つオブジェクトがあり、定数値にもabcのオブジェクト値があると単純に考えることができます。

ストリングスプライシング

一定のスプライシング

文字列のスプライシングには主に2つの方法があります。1つは定数と定数のスプライシングです。
ここに画像の説明を挿入します

バイトコードから、「a」+「b」+「c」は実際には「abc」と同等であることがわかります。これはコンパイラの最適化によるものです。コンパイルすると、「a」+「b」+「」になります。 c "ステッチが完了しました。したがって、s1とs2は両方とも文字列定数プール内の同じアドレスを指し、s1 == s2の結果は真です。

可変スプライシング

もう1つは、変数と定数のスプライシング、または結果オブジェクトの1つがオブジェクトである場合、スプライシングの結果は、上記の定数プールではなくヒープにあります。バイトコードによるスプライシングを見てみましょう。原則。
ここに画像の説明を挿入します

変数スプライシングはStringBuilderオブジェクトを作成し、次にStringBuilderのappendメソッドを介して文字列スプライシングを実行し、最後にtoString()メソッドを呼び出すことがわかりました。

次に、StringBuilderのtoString()メソッドをクリックします。
ここに画像の説明を挿入します

Stringオブジェクトが作成されたことがわかりましたが、これは以前に使用した新しいString( "abc")とは異なります。StringBuilderのtoStringは、ldcコマンドなしでオブジェクトを作成するだけです。つまり、オブジェクトはで作成されません。一定のプール。これは非常に重要です。

最後に、==が等しいかどうかを比較すると、一方はヒープ内のアドレスであり、もう一方は文字列定数プール内のアドレスであるため、当然のことながら真ではありません。

展開

スプライシングについて話しているのでnew String( "a")+ new String( "b")について考える、いくつのオブジェクトが作成されますか?

まず、前述のnew String( "a")の例によれば、彼は2つのオブジェクトを作成するので、2つのnewは4つのオブジェクトを作成し、最後にスプライシングを追加すると、StringBuilderが作成されて5つのオブジェクトになり、最後にStringBuilder.toString()ここでのtoString()の新しいString()はヒープ内にオブジェクトを作成するだけであり、定数プールには「ab」がないことに注意してくださいしたがって、合計6つのオブジェクトが必要です。以下のバイトコードを見てください。
ここに画像の説明を挿入します

 /* new String("a") + new String("b")呢?
 *  对象1:new StringBuilder()
 *  对象2: new String("a")
 *  对象3: 常量池中的"a"
 *  对象4: new String("b")
 *  对象5: 常量池中的"b"
 *
 *  深入剖析: StringBuilder的toString():
 *      对象6 :new String("ab")
 *       强调一下,toString()的调用,在字符串常量池中,没有生成"ab"
 */

インターン()

ここに画像の説明を挿入します

ドキュメントを読むことで、intern()の役割を簡単に要約できることがわかります。

比如Strings1 = new String(“ abc”); String s2 = s1.intern();

s1.intern()の機能は、文字列定数プールに「abc」があるかどうかを確認することです。ある場合は、文字列定数プール内の文字列のアドレスを返します。ない場合は、「abc」を作成します。文字列定数プール。このアドレスが返されるため、s2は文字列定数プール内の「abc」のアドレスを指します。これにより、メモリ内に文字列のコピーが1つだけ存在するようになり、メモリスペースを節約できます。ヒープ内にオブジェクトが繰り返し作成されないように、新しいString( "abc")。intern()を複数回作成します。

バージョンの違い

intern()jdkのバージョンが異なると、特定の実装も異なります。jdk7以降、新しいオブジェクトがヒープ内に作成されるため、文字列定数プールがメソッド領域(永続生成)からヒープに移動されることがわかっています。 、次にinternを使用すると()、ヒープ内の2つのスペースの内容が実質的に同じになるため、スペースが無駄になります。また、jdk6、メソッド領域とヒープが異なるため、この節約はできません。完了しました。これらの2つの状況では、2つの方法があります。

String intern()の使用を要約します

  • jdk 1.6では、この文字列オブジェクトを文字列プールに入れてみてください。
    • 文字列プールにある場合、配置されません。既存の文字列プール内のオブジェクトのアドレスを返します。
    • そうでない場合は、このオブジェクトのコピーを作成し、それを文字列プールに入れて、文字列プール内のオブジェクトのアドレスを返します。
  • jdk 1.7以降では、この文字列オブジェクトの文字列プールを配置しようとします。
    • 文字列プールにある場合、配置されません。既存の文字列プール内のオブジェクトのアドレスを返します。
    • そうでない場合は、このオブジェクトの参照アドレスのコピー作成し、それを文字列プールに入れて、文字列プール内のオブジェクトアドレスを返します。

jdk1.6:
ここに画像の説明を挿入します

jdk1.7から:
ここに画像の説明を挿入します

面接の質問

@Test
public void test5() {
    
    
    //String s1 = new String("ab");//执行完以后,会在字符串常量池中会生成"ab"
    String s1 = new String("a") + new String("b");执行完以后,不会在字符串常量池中会生成"ab"
    s1.intern();
    String s2 = "ab";
    System.out.println(s1 == s2);
}

s1には2つの形式があります。

1つは新しいString( "ab");です。

2番目はnewString( "a")+ new String( "b");です。

それらの違いは、前述のとおり、文字列定数プールに「ab」があるかどうかです。その後、intern()の呼び出しは、jdkのバージョンに応じて変化し、結果も変化します。

総括する

この記事を読んだ後、最初に言ったように、Stringについて別の見方がありますか?高校の数学の先生はしばしば概念、概念について話します。主題を何万回も変えて、最終的に元の本質に戻してみましょう。何が起こっているのか、そしてその理由を知る必要があります。マスターシャンシリコンバレーによるJVMチュートリアルを参照することをお勧めします。 Javaの異なる理解。以前はあいまいだったコンテンツの明確な理解があります。

あなたの周りの人や物と同じように、あなたはそれを本当に理解していますか?

この記事は時間がかかりました。参考になった場合は、クリックして注意を払うことをお勧めします。今日の上海証券取引所指数は3600だと聞きましたが、少しパニックになりました。

知れば知るほど、知らない。

おすすめ

転載: blog.csdn.net/weixin_44226263/article/details/112540490