Javaの文字列キー分析

  1. String s = new String("abc")このコードはいくつのオブジェクトを作成しますか?s=="abc"この判断の結果は何ですか?s.substring(0,2).intern()=="ab"これの結果は何ですか?
  2. s.charAt(index)は、対応するすべての文字を実際に表すことができますか?
  3. "abc"+"gbn"+s直接文字列スプライシングのパフォーマンスは、StringBuilderを使用するよりも本当に低いですか?

序文

はじめまして〜

JavaのStringオブジェクト機能はc / c ++言語とは大きく異なり、重要な点はその不変性です。したがって、文字列の不変性の設計に役立つために、多くの関連する質問が発生しました。なぜそれを不変に保つのですか?文字列を下部に保存する方法は?文字列操作を実行してパフォーマンスを向上させる方法は?などなど。さらに、文字エンコードの知識も非常に重要です。結局のところ、emoijの使用は今では普通です。

この不変に関する記事の内容は、拡張に焦点を当てています。

  1. Stringオブジェクトの不変性を分析します。
  2. コンスタントプールのストレージ原理とインターンメソッドの原理
  3. 文字列スプライシングの原理と最適化
  4. コードユニットとコードポイントの違い
  5. 総括する

さあ、始めましょう〜

不変性

文字列の不変性を理解するために、コードの数行を簡単に見ることができます。

String string = "abcd";
String string1 = string.replace("a","b");
System.out.println(string);
System.out.println(string1);

输出:
abcd
bbcd

string.replace("a","b")このメソッドは、置き換え"abcd"真ん中のaものをb出力から、元の文字列stringがまったく変更されていないことがわかります。replaceメソッドは新しい文字列"bbcd"作成し、それをstring1変数に割り当てます。これはStringの不変性です。

別の栗:変更し、最後"abcd"の文字dにはa、C / C ++言語では、あなたは直接最後の文字を変更することができ、Javaで、あなたは:, Stringオブジェクトを再作成する必要がabcaあるため"abcd"、それは不変であると、変更を変更することはできません。

Stringオブジェクトの値は不変であり、すべての操作でStringの値が変更されることはありませんが、新しい文字列を作成して文字列操作を実装します。

Javaがこのように設計されている理由を理解するのは難しいことがよくありますが、パフォーマンスの低下を引き起こしませんか?文字列を日常的に使用しているシーンを振り返ると、文字列を直接変更するのではなく、一度使用してから破棄することがよくあります。しかし、次回は、おそらく同じStringオブジェクトが再び使用されます。例:ログ印刷:

Log.d("MainActivity",string);

フロント"MainActivity"では彼を変更する必要はありませんが、このストリングによく使用されます。Javaは、データの一貫性を維持するためだけにStringを不変になるように設計しているため、同じリテラルStringが同じオブジェクトを参照します。例えば:

String s1 = "hello";
String s2 = "hello";

s1これs2は、参照と同じStringオブジェクトです。文字列が可変の場合、この設計は実現できません。したがって、作成したStringオブジェクトを再作成せずに再利用できます。

Stringの再利用は、Stringを変更するシナリオ以上のものであるという前提に基づいて、Javaは、データの一貫性を維持するためにStringを不変に設計し、同じリテラル文字列が同じStringオブジェクトを参照し、既存のStringオブジェクトを再利用できるようにします。 。

別の見方は、「Javaプログラミングの考え」という本に記載されています。まず、次のコードを確認します。

public String allCase(String s){
    
    
    return string.toUpperCase();
}

allCaseこのメソッドは、すべての着信Stringオブジェクトを大文字に変換し、変更された文字列を返します。現時点では、呼び出し元の期待は、着信Stringオブジェクトが情報を提供するためにのみ使用され、変更されることを望まないことです。したがって、Stringの不変の特性はこれに非常によく適合します。

Stringオブジェクトをパラメーターとして使用する場合、Stringオブジェクト自体を変更しないことを望んでおり、Stringの不変性はこの点を満たしています。

保管の原則

Stringオブジェクトは不変であるため、ストレージ内の通常のオブジェクトとも異なります。Stringオブジェクトではなく、ヒープ作成されオブジェクトは実際には同じであり、同じではなく、の定数プールも格納されいることは誰もが知っていますヒープ領域の文字列オブジェクトはGC中にリサイクルされる可能性が高く、定数プールの文字列オブジェクトは簡単にリサイクルされないため、定数プールの文字列オブジェクトを再利用できます。言い換えると、定数プールは、Stringオブジェクトを再利用できる基本的な理由です。

定数プールは簡単にガベージコレクションされないため、定数プール内のStringオブジェクトは常に存在し、再利用できます。

定数プールにStringオブジェクトを作成するには、2つの方法があります。明示的に二重引用符を使用intern()して文字列オブジェクトを作成する方法と、Stringオブジェクトメソッドを使用する方法です。これらの2つのメソッドは、必ずしも定数プールにオブジェクトを作成するわけではありません。同じオブジェクトが定数プールにすでに存在する場合は、オブジェクトへの参照を直接返し、Stringオブジェクトを再利用します。Stringオブジェクトを作成する他の方法は、ヒープ領域にStringオブジェクトを作成することです。栗をあげます。

new String()メソッドを渡すか、メソッドなどのStringオブジェクトのインスタンスメソッドを呼び出すstring.substring()と、ヒープ領域にStringオブジェクトが作成されます。また、たとえばString s = "abc"二重引用符を使用して文字列オブジェクトを作成したりintern()Stringオブジェクトのメソッドを呼び出したりすると、次の図に示すように、オブジェクトが定数プールに作成されます。

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-RncIV1I4-1608366801946)(https://i.loli.net/2020/ 12/18 / 9TksK84mfIUOFxt.png)]

記事の冒頭の質問を覚えていますか?

  • String s = new String("abc")、このコードはいくつのオブジェクトを作成しますか?"abc"オブジェクトは定数プール内に構築され、new String()メソッドはヒープ領域にオブジェクトを作成するため、合計2つあります。
  • s=="abc"結果は誤りです。2つの異なるオブジェクト。1つはヒープにあり、もう1つは定数プールにあります。
  • s.substring(0,2).intern()=="ab"インターンメソッドは、定数プールに値「ab」のStringオブジェクトを作成します。「ab」ステートメントは、新しいStringオブジェクトを作成しませんが、既存のStringオブジェクトを返します。したがって、結果は真です。

のみ明示的に使用する二重引用符は、Stringオブジェクトの使用して文字列オブジェクトを構築するintern()方法どちらの方法は、Stringオブジェクトの定数プールを作成し、他の方法は、ヒープ内のオブジェクトを作成することです。定数プールにStringオブジェクトを作成する前に、同じStringオブジェクトが存在するかどうかをチェックし、存在する場合は、オブジェクトを再作成せずにオブジェクトへの参照を直接返します。

言及する必要があるインターンメソッドについて別の質問があります。異なるjdkバージョンで実行される特定のロジックは異なります。jdk6以前は、メソッド領域はヒープ領域とは別の不滅メモリ領域に格納されていました。定数プールにオブジェクトを作成する場合は、ディープコピーが必要です。つまり、オブジェクトは完全にコピーされて作成されます。新しいオブジェクト、以下に示すように:

image.png

不滅の世代には深刻な欠点があります。OOMが発生しやすいということです。不滅の生成にはメモリの上限があり、非常に小さいです。プログラムがインターンメソッドを大量に呼び出すと、OOMが発生しやすくなります。JDK7では、定数プールが不滅の世代から移行され、ヒープ領域に実装されました。jdk8の後、ローカルスペースが使用されます。jdk7の後に定数プールを実装すると、浅いコピー用に定数プールにオブジェクトを作成できます。つまり、オブジェクト全体をコピーする必要はなく、オブジェクトの参照をコピーするだけで、オブジェクトの繰り返し作成を回避できます。以下に示すように、オブジェクト:

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-OFGKRbf0-1608366801958)(https://i.loli.net/2020/ 12/19 / SnlrAHViRKzkb7C.png)]

このコードを観察してください:

String s = new String(new char[]{
    
    'a'});
s.intern();
System.out.println(s=="a");

jdk6の前に2つの異なるオブジェクトが作成され、出力はfalseでした。jdk7の後、定数プールに新しいオブジェクトは作成されず、同じオブジェクトが参照されたため、出力はtrueでした。

jdk6より前は、internを使用してオブジェクトのディープコピーを作成し、jdk7以降は、ヒープ領域でStringオブジェクトを再利用するためにシャローコピーを使用していました。

上記の分析により、Stringは、二重引用符を使用して直接作成された文字列を実際に再利用します。インターンメソッドは定数プールで文字列参照を返すことができますが、ヒープ領域にStringオブジェクトがすでに必要です。したがって、結論を導き出すことができます。

二重引用符を使用して文字列を明示的に作成してみてください。文字列を頻繁に再利用する必要がある場合は、internメソッドを呼び出して定数プールに格納できます。

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

最も一般的な文字列操作は文字列スプライシングです。Stringオブジェクトは不変であるため、スプライシングごとに新しい文字列オブジェクトを作成する必要がある場合、パフォーマンスに大きな影響を与えます。したがって、StringBufferとStringBuilderの2つのカテゴリが正式にリリースされましたこれらの2つのクラスは、新しいStringオブジェクトを作成せずに、文字列をアセンブルおよび変更できます。次のコード:

StringBuilder stringBuilder = new StringBuilder("abc");
stringBuilder.append("p")
        .append(new char[]{
    
    'q'})
        .deleteCharAt(2)
        .insert(2,"abc");
String s = stringBuilder.toString();

スプライシング、挿入、削除はすべて非常に迅速に行うことができます。したがって、StringBuilderを使用して、文字列を初期化するための変更、スプライス、およびその他の操作を使用する方が効率的です。StringBufferとStringBuilderのインターフェースは同じですが、StringBufferは、対応するパフォーマンス価格を支払いながらスレッドの安全性を確保するために、操作メソッドにsynchronizeキーワードを追加します。シングルスレッド環境ではStringBuilderを使用することをお勧めします。

StringBuilderとStringBufferを使用して、スプライシングおよび変更操作中に文字列を初期化すると、パフォーマンスを向上させることができます。シングルスレッド環境ではStringBuilderを使用する方が適切です。

通常の状況では、+接続文字列を使用します。+Javaで演算子をオーバーロードした後、文字列を連結するために使用できます。コンパイラーは+、一連の最適化実行しました。次のコードを確認してください。

String s1 = "ab"+"cd"+"fg";
String s2 = "hello"+s1;

Object object = new Object();
String s3 = s2 + object;
  • s1文字列の場合、コンパイラは"ab"+"cd"+"fg"それを直接に最適化し"abcdefg"String s1 = "abcdefg";同等です。この最適化により、スプライシング中の消費も削減されます。StringBuilderを使用するよりもさらに効率的です。

  • s2のスプライシングコンパイラは、文字列を構築するためのStringBuilderを自動的に作成します。これは、次のコードと同等です。

    StringBuilder sb = new StringBuilder();
    sb.append("hello");
    sb.append(s1);
    String s2 = sb.toString();
    

    つまり、StringBuilderを明示的に使用する必要がないということですか、とにかくコンパイラが最適化に役立ちますか?もちろんそうではありません。以下のコードに従ってください。

    String s = "a";
    for(int i=0;i<=100;i++){
          
          
        s+=i;
    }
    

    ここには100個のループがあり、100個のStringBuilderオブジェクトが作成されます。これは明らかに非常に間違ったアプローチです。このとき、StringBuilderオブジェクトの作成を表示する必要があります。

    StringBuilder sb = new StringBuilder("a");
    for(int i=0;i<=100;i++){
          
          
        sb.append(i);
    }
    String s = sb.toString();
    

    StringBuilderオブジェクトを作成するだけで、パフォーマンスが大幅に向上します。

  • String s3 = s2 + object;文字列スプライシングは、通常のオブジェクトの直接スプライシングもサポートしています。このとき、オブジェクトのメソッドが呼び出さtoStringれ、スプライシング用の文字列が返されます。toStringこのメソッドはObjectクラスのメソッドです。サブクラスがオーバーライドされていない場合、ObjectクラスのtoStringメソッドが呼び出され、デフォルトでクラス名+参照アドレスが出力されます。これは問題ではないようですが、大きな落とし穴があります。toStringメソッドで+スプライシング自体を直接使用しないように注意してください。次のコード

    @Override
    public String toString() {
          
          
        return this+"abc";
    }
    

    ここでこれを直接スプライシングすると、これのtoStringメソッドが呼び出され、無限再帰が発生します。

Javaは+スプライシング文字列を最適化しました:

  • 通常のオブジェクトは直接接合できます
  • リテラルを直接スプライシングすると、リテラルが合成されます
  • 通常のスプライシングはStringBuilderを使用して最適化します

ただし、同時に、これらの最適化には制限があり、パフォーマンスを向上させるには、適切なシーンで適切なステッチ方法を選択する必要があることに注意してください。

コーディングの問題

Javaでは、一般に、charオブジェクトは文字を格納でき、charのサイズは16ビットです。しかし、コンピューターの発展に伴い、文字セットも絶えず進化しています.16ビットのストレージサイズではもはや十分ではないため、emoijなどの特殊文字を格納するために2文字(32ビット)が使用されます。16ビットはコードの単位と呼ばれ、文字コードポイントと呼ばれます。コードポイントはコードの単位を占める場合もあれば、2つの場合もあります。

文字列では、String.length()メソッドを呼び出すと、返されるのはコードユニットの数であり、String.charAt()返されるのは添え字に対応するコードユニットでもあります。これは通常の状況では問題ではありません。また、特殊文字の入力が許可されている場合、これは大きな問題です。コードポイントの実数を取得するには、String .codePointCountメソッドを呼び出すことができString.codePointAtます。ポイントに対応するコードを取得するには、呼び出し可能なメソッドを使用します。拡張文字セットとの互換性を保つため。

文字はコードポイントであり、文字はコードユニットと呼ばれます。コードポイントは、1つまたは2つのコードユニットを占める場合があります。特殊文字が許可されている場合は、文字列を操作する単位としてコードポイントを使用する必要があります。

総括する

この時点で、Stringに関するいくつかの重要な質問が分析され、記事の冒頭にある質問の読者も回答を知っている必要があります。これらは一般的なインタビューの質問であり、Stringの焦点です。また、正規表現、入出力、一般的に使用されるAPIなどもStringに関連する非常に重要なコンテンツであり、興味のある読者は自分で学ぶことができます。

この記事がお役に立てば幸いです。

参照

  • 「JavaProgrammingThoughts」Javaエンジニアに知られている聖典で、Javaを使用してプログラミングのアイデアをプログラミングおよび体験する方法を詳細に説明しています。
  • 「JavaCoreTechnology Volume One」の入門書では、主にStringAPIの使用方法といくつかの注意点について説明しています。
  • 「JVMの詳細な理解」は、メソッド領域と定数プールを理解するのに非常に役立ちます。
  • String#internの技術チームによるString.internメソッドに関する記事の詳細な分析
  • インターネット上の他のブログの貢献に感謝します。

これは全文です。オリジナルになるのは簡単ではありません。役立つ場合は、気に入ったり、ブックマークしたり、コメントしたり、転送したりできます。
作者は非常に知識が豊富で、何かアイデアがあれば、コメントセクションで気軽に連絡して修正してください。
転載が必要な場合は、コメント欄またはプライベートメッセージ交換をお願いします。

私の個人ブログへようこそ:ポータル

おすすめ

転載: blog.csdn.net/weixin_43766753/article/details/111406859