Java の String、StringBuilder、StringBuffer について話しましょう

これら 3 つについて、最初にわかることは、String は不変であり、StringBuilder と StringBuffer は変更可能であるということです。そこで、まず String について、なぜ不変になるように設計されているのか、そして不変性を実現する方法について説明します。

String が不変になるように設計されているのはなぜですか?

実際、開発プロセスで最も一般的に使用されるデータ構造は文字列であると実感していますが、従来のオブジェクト作成方法に依存すると、文字列値が繰り返されるオブジェクトが多数存在し、大量のスペースを消費することになります。 GC 効率に影響します。

したがって、不変に設計されている場合、同じ値を持つ複数のオブジェクトの参照は文字列オブジェクトを指すことになり、ヒープ メモリを大幅に削減できます。同時に、ハッシュ値は文字列にキャッシュされるため、ハッシュの使用に影響を与え、パフォーマンスを大幅に向上させます

同時に、不変になるように設計されている場合、スレッドセーフです。他のスレッドが値を変更した場合でも、現在の値を変更するのではなく、既存のオブジェクトを作成または参照します。同時に、それは非常に安全でもあり、不変のコンテンツは信頼できると考えられますが、その値が自由に変更できる場合、あまりにも信頼できません。

String デザインはどのようにして不変性を実現するのでしょうか?

まずはjdk1.8のソースコードを見てみましょう。

arduino

复制代码

public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); } public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); } }

ソース コードから、文字列は、final で変更された char 配列に格納されていることがわかります。これは、文字配列が不変であり、substring メソッドと concat メソッドが実際に new String() を返すことを意味します。

拡大する

Java 9 以降では、文字列を格納するための新しい構造 (バイト配列) が追加されており、これは実際には最適化の層です。そして、なぜこれを行うのでしょうか? Java は内部的にエンコードに UTF-16 を使用します。つまり、1 つの文字が 1 バイトで識別できる場合でも、UTF-16 を使用した後でも 2 バイトが占有されることになります。これは実際には時間の無駄であり、多くの場合、文字列は実際、これらはすべて LATIN-1 (ASCLL を含む 128 文字を識別できるシングルバイト エンコード スキーム) を使用してエンコードできます。そこで、「コンパクトストリング」という概念が導入されました。では、UTF-16 をいつ使用するか、LATIN-1 を使用するかをどのように区別するのでしょうか?

coderという名前のフィールドは String クラスで定義されており、文字列のエンコード内容を保存するために使用され、型に応じて異なる記憶構造に格納されます。関連する IndexOf メソッドでは、このフィールドを決定する必要があります。対応する文字が見つかりました。

new String() を呼び出したときに何をしましたか? オブジェクトを作成するだけですか?

Java オブジェクトは、JVM に格納されるときに特定の構造 (つまり、オブジェクト モデル)を持ち、これには 2 つの情報も含まれます。1 つは、スレッド、ロック識別子などの実行時情報を格納するオブジェクト ヘッダー、もう 1 つはオブジェクト ヘッダーです。残りの部分はメタデータ、 はクラス情報へのポインタ このJVMの便利な知識については後ほど別途書きます。

実際には、何があっても、 new を使用するときはヒープ上にオブジェクトを作成しますが、文字列には特殊なケースがあります。この特殊なケースは、定数プール内の文字列定数です。この文字列は、クラスのコンパイル中に実際に入力されます。クラスが ClassLoader によって初めてロードされるとき、クラスはクラス定数プールからランタイム定数プールに入ります (1.8 以降、文字列定数プールは管理を改善するためにヒープに移動されました)オブジェクトを削除し、メモリ リークを防ぎます。)。文字列定数プールには、文字列参照とオブジェクトが格納されます。参照は文字列テーブルに格納され、 new String() はヒープ上のオブジェクト インスタンスとして出てきます。その参照は、参照される文字列定数プール内の文字です。文字列参照。したがって、文字列定数プールにそのようなオブジェクトが存在しない場合は、ヒープ インスタンスに 1 つと文字列定数プールに 1 つずつ、計 2 つのオブジェクトを作成できることがわかります。このオブジェクトは定数プールに存在しますか?

インターン

文字列定数プールについては、上記でよく言及されていますが、文字列定数プールの最も一般的な説明は、次のコードのように、プログラムの実行中に結果がわかる文字列であるというものです。

java

复制代码

public static void main(String[] args) { String a = "abc"; String b = "def"; String c = "abc"+"def"; }

逆コンパイルの結果は String c="abcdef" になります。2 つの定数に + が使用されている場合、それらは定数になります。変数を追加する別の方法


java

复制代码

public static void main(String[] args) { String a = "abc"; String b = "def"; String c = a+b; }

逆コンパイルの結果は、


java

复制代码

String c = (new StringBuilder()).append(a).append(b).toString();

計算結果の値が定数プールに入らないのですが、このような文字列がよく使われます。インターンの役割が反映されているわけですね。これには 2 つの機能があり、1 つは定数プールにこの文字列がない場合にこの値を文字列定数プールに追加すること、もう 1 つはこの定数への参照を返すことです。

もう一度展開します --> 文字列には長さの制限がありますか?

答えは「はい」ですが、それは異なります。コンパイル時の String の最大長は 65535 で、実行時の最大長は int 2^31-1 の最大値です。これには Java 仮想マシンの仕様が関係しますが、大まかに言うと、仮想マシン内で文字列定数を表現するために CONSTANT_Utf8_info 構造体が使用されます。 2 を識別します。符号なしのバイト数。1 バイトは 8 ビット、2 バイトは 16 ビットであるため、最大値は 2^16-1 = 65535 です。

StringBuilder と StringBuffer は両方とも変更可能であり、StringBuffer はスレッドセーフです。

StringBuilder と StringBuffer は両方とも、2 つのプロパティを持つ AbstractStringBuilder を継承します。

java

复制代码

char[] value; /** * The count is the number of characters used. */ int count;

そして、それらはどれもfinalによって変更されておらず、変数であることを示しているため、追加のソースコードを見てください。


java

复制代码

public AbstractStringBuilder append(StringBuffer sb) { if (sb == null) return appendNull(); int len = sb.length(); ensureCapacityInternal(count + len); sb.getChars(0, len, value, count); count += len; return this; }

実際には、容量の拡張とキャラクターの配置という 2 つのことを行います。append メソッドは StringBuffer でオーバーライドされます


java

复制代码

@Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }

Synchronized は、これがスレッドセーフなメソッドであることを示すために追加されます。

おすすめ

転載: blog.csdn.net/m0_71777195/article/details/132975014