回答:
Javaは最初から並行性を考慮して定義されました。よく言及されるように、共有されたミュータブルには問題があります。あることは、別のスレッドの背後で別のスレッドを変更することができます。
共有文字列のために発生した多数のマルチスレッドC ++バグがあります。コード内の別のモジュールがポインターを保存し、同じままであると予想したときに、あるモジュールが変更しても安全だと判断した場合です。
これに対する「解決策」は、すべてのクラスがそれに渡される可変オブジェクトの防御コピーを作成することです。可変文字列の場合、これはコピーを作成するO(n)です。不変の文字列の場合、コピーを作成することはO(1)です。これはコピーではなく、変更できない同じオブジェクトだからです。
マルチスレッド環境では、不変オブジェクトを常に安全に共有できます。これにより、メモリ使用量が全体的に削減され、メモリキャッシュが向上します。
多くの場合、コンストラクターへの引数として文字列が渡されます-最も簡単に思いつくのは、ネットワーク接続とプロトコルです。実行後の不特定の時点でこれを変更できると、セキュリティの問題につながる可能性があります(この機能は、あるマシンに接続していると考えていましたが、別のマシンに流用されましたが、オブジェクトのすべてが最初のマシンに接続されているように見えます...その同じ文字列)。
Javaではリフレクションを使用できます。このためのパラメーターは文字列です。反映する別のメソッドへの途中で変更される可能性のある文字列を渡す危険性 これは非常に悪いです。
ハッシュテーブルは、最もよく使用されるデータ構造の1つです。多くの場合、データ構造のキーは文字列です。不変の文字列があるということは、(上記のように)ハッシュテーブルが毎回ハッシュキーのコピーを作成する必要がないことを意味します。文字列が可変であり、ハッシュテーブルがこれを行わなかった場合、ハッシュキーをある距離で変更できる可能性があります。
javaのオブジェクトが機能する方法は、すべてがハッシュキーを持っていることです(hashCode()メソッドを介してアクセスされます)。不変の文字列を持つことは、hashCodeをキャッシュできることを意味します。文字列がハッシュのキーとして使用される頻度を考慮すると、これによりパフォーマンスが大幅に向上します(毎回ハッシュコードを再計算する必要がなくなります)。
Stringを不変にすることにより、データ構造を支える基になる文字配列も不変になります。これにより、substring
メソッドの特定の最適化を実行できます(必ずしも実行されるとは限りません-また、いくつかのメモリリークの可能性が生じます)。
もしあなたがそうするなら:
String foo = "smiles";
String bar = foo.substring(1,5);
の値bar
は「マイル」です。ただし、両方foo
をbar
同じ文字配列でサポートし、文字配列内の異なる開始点と終了点を使用するだけで、より多くの文字配列のインスタンス化を減らすか、コピーすることができます。
foo | | (0、6) vv 笑顔 ^ ^ バー| | (1、5)
さて、その欠点(メモリリーク)は、1kの長さの文字列があり、最初と2番目の文字の部分文字列を取得した場合、1kの長さの文字配列によって裏付けられることです。文字配列全体の値を持つ元の文字列がガベージコレクションされた場合でも、この配列はメモリに残ります。
JDK 6b14の文字列でこれを見ることができます(次のコードはGPL v2ソースからのものであり、例として使用されています)
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.offset = 0;
this.count = count;
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
部分文字列がパッケージレベルのStringコンストラクターを使用する方法に注意してください。このコンストラクターは配列のコピーを伴わず、はるかに高速になります(大規模な配列を複製することはありませんが、大規模な配列を保持する可能性があります)。
上記のコードはJava 1.6用であることに注意してください。サブストリングコンストラクターの実装方法は、Java 1.7.0_06で行われた「ストリング内部表現の変更」に記載されているように、Java 1.7で変更されました
-上記のメモリリークに関する問題。Javaは、多くの文字列操作を持つ言語とは見なされなかったため、部分文字列のパフォーマンスの向上は良いことでした。今、収集されない文字列に格納された巨大なXMLドキュメントでは、これが問題になります...したがって、String
サブストリングで同じ基本配列を使用しないように変更し、より大きな文字配列をより迅速に収集できるようにします。
一つは、可能性があり、文字列の値の周りの代わりに可変性の問題を回避するために、不変の文字列への参照を渡します。ただし、大きな文字列の場合、これをスタックに渡すと...システムに悪用されます(xmlドキュメント全体を文字列としてスタックに置き、それらを取り出したり、引き渡し続けたりします)。
確かに、これはStringsが不変である理由の最初の動機ではありませんでしたが、不変のStringsが良い理由である理由を検討するとき、これは確かに考慮すべきものです。
文字列を少し使ったことがある人なら誰でも、メモリを吸い取ることができることを知っています。これは、しばらく使用されているデータベースからデータをプルするようなことをしている場合に特に当てはまります。多くの場合、これらの刺し傷は、何度も何度も同じ文字列です(各行に1回)。
現在、多くの大規模なJavaアプリケーションがメモリのボトルネックになっています。これらのタイプのアプリケーションのJavaヒープライブデータセットの約25%がStringオブジェクトによって消費されることが測定により示されています。さらに、これらのStringオブジェクトの約半分は重複しています。重複とは、string1.equals(string2)がtrueであることを意味します。ヒープ上で文字列オブジェクトを複製することは、本質的にメモリの無駄です。...
Java 8 update 20では、これに対処するためにJEP 192(上記引用の動機)が実装されています。文字列の重複排除がどのように機能するかの詳細に入ることなく、文字列自体が不変であることが不可欠です。StringBuilderは重複する可能性があり、誰かがあなたの下から何かを変更したくないので、重複排除できません。不変の文字列(その文字列プールに関連する)は、通過できることを意味し、同じ文字列が2つ見つかった場合、一方の文字列参照を他方にポイントし、ガベージコレクターに新しく使用されていない文字列を消費させることができます。
Objective C(Javaより前のバージョン)にはとがNSString
ありNSMutableString
ます。
C#と.NETは、デフォルトの文字列が不変であるという同じ設計選択を行いました。
Lua文字列も不変です。
Pythonも。
歴史的に、Lisp、Scheme、Smalltalkはすべて文字列をインターンしているため、不変です。より現代的な動的言語では、文字列を不変にする必要がある何らかの方法で文字列を使用することがよくあります(Stringではない場合がありますが、不変です)。
これらの設計上の考慮事項は、多数の言語で何度も何度も行われています。不変な文字列は、その不器用さのすべてについて、代替よりも優れており、コードの改善(バグの削減)および実行可能ファイル全体の高速化につながるというのが一般的なコンセンサスです。
Javaデザイナーは、Stringがあらゆる種類のJavaアプリケーションで最もよく使用されるデータ型になることを知っているため、最初から最適化することを望んでいました。その方向の重要なステップの1つは、文字列リテラルを文字列プールに格納するというアイデアでした。目標は、一時的な文字列オブジェクトを共有することで減らすことであり、共有するには不変クラスからのものでなければなりません。相互に不明な2つのパーティと可変オブジェクトを共有することはできません。2つの参照変数が同じStringオブジェクトを指している架空の例を見てみましょう。
String s1 = "Java";
String s2 = "Java";
s1がオブジェクトを「Java」から「C ++」に変更した場合、参照変数も値s2 = "C ++"を取得しますが、それについては知りません。Stringを不変にすることにより、このStringリテラルの共有が可能になりました。つまり、JavaでStringをfinalまたはImmutableにしないと、Stringプールの重要なアイデアを実装できません。
Javaは、すべてのレベルのサービスで安全な環境を提供するという点で明確な目標を持っています。Stringは、これらのセキュリティ全体で重要です。文字列は、多くのJavaクラスのパラメータとして広く使用されています。たとえば、ネットワーク接続を開くために、ホストとポートを文字列として渡すことができます。データベースURLを文字列として渡します。Stringが不変ではない場合、ユーザーはシステム内の特定のファイルへのアクセスを許可した可能性がありますが、認証後にPATHを別のものに変更できるため、深刻なセキュリティ問題が発生する可能性があります。同様に、データベースまたはネットワーク内の他のマシンに接続しているときに、文字列値を変更するとセキュリティ上の脅威になる可能性があります。可変文字列は、Reflectionでもセキュリティ問題を引き起こす可能性がありますが、
StringをfinalまたはImmutableにしたもう1つの理由は、クラスローディングメカニズムで頻繁に使用されていたという事実によるものです。Stringは不変ではないため、攻撃者はこの事実を利用して、java.io.Readerなどの標準Javaクラスのロード要求を悪意のあるクラスcom.unknown.DataStolenReaderに変更できます。Stringをfinalかつ不変に保つことにより、少なくともJVMが正しいクラスをロードしていることを確認できます。
並行性とマルチスレッドはJavaの主要な製品であるため、Stringオブジェクトのスレッドセーフについて考えることは非常に理にかなっています。Stringが広く使用されることが予想されていたため、Immutableにすることは外部同期を行わないことを意味し、複数のスレッド間でStringを共有するコードがよりクリーンになります。この単一の機能により、すでに複雑で混乱しやすく、エラーが発生しやすい同時実行コーディングがはるかに簡単になります。Stringは不変であり、スレッド間で共有するだけなので、コードは読みやすくなります。
これで、クラスを不変にすると、作成したクラスは変更されないことが事前にわかります。これにより、キャッシングなどの多くのパフォーマンス最適化のオープンパスが保証されます。文字列自体は、私が変更するつもりはないことを知っているので、文字列はハッシュコードをキャッシュします。さらに、ハッシュコードを遅延計算し、一度作成したら、キャッシュするだけです。単純な世界では、StringオブジェクトのhashCode()メソッドを最初に呼び出すと、ハッシュコードが計算され、その後のhashCode()の呼び出しはすべて、計算済みのキャッシュされた値を返します。これにより、HashtableやHashMapなどのハッシュベースのマップでStringが頻繁に使用されるため、パフォーマンスが向上します。ハッシュコードのキャッシングは、文字列自体の内容に依存するため、不変で最終的なものにするまで不可能でした。
Java Virtual Machineは、文字列操作に関していくつかの最適化を実行しますが、それ以外の方法では実行できませんでした。たとえば、値が「Mississippi」の文字列があり、「Mississippi」.substring(0、4)を別の文字列に割り当てた場合、知っている限り、「Miss」を作成する最初の4文字のコピーが作成されました。 。どちらも同じ元の文字列「ミシシッピ」を共有し、一方が所有者であり、もう一方が位置0から4までのその文字列の参照であることがわかりません(所有者への参照は、所有者が範囲外になったときのガベージコレクター)
これは、「ミシシッピ」ほど小さい文字列では簡単ですが、より大きな文字列と複数の操作がある場合、文字列をコピーする必要がないため、時間を大幅に節約できます。文字列が可変の場合、元の文字列を変更するとサブ文字列「コピー」にも影響するため、これを行うことはできません。
また、ドナルが言及しているように、利点はその欠点によって大きく評価されます。ライブラリに依存するプログラムを作成し、文字列を返す関数を使用するとします。その値が一定のままであることをどのようにして確認できますか?そのようなことが起こらないようにするには、常にコピーを作成する必要があります。
同じ文字列を共有する2つのスレッドがある場合はどうなりますか?現在別のスレッドによって書き換えられている文字列を読み取りたくないでしょうか?そのため、文字列はスレッドセーフである必要があります。スレッドセーフは一般的なクラスであり、事実上すべてのJavaプログラムを非常に遅くします。それ以外の場合、その文字列を必要とするすべてのスレッドのコピーを作成するか、その文字列を使用するコードを同期ブロックに配置する必要がありますが、どちらもプログラムの速度を低下させるだけです。
これらすべての理由から、それはC ++との差別化を図るためにJavaに対して行われた初期の決定の1つでした。
文字列が不変である理由は、言語の他のプリミティブ型との一貫性にあります。あなたがいる場合はint
値42を含む、あなたがそれに値1を追加し、あなたは、開始値とは全く無関係である新しい値、43を得る42を変更しないでください。文字列以外のプリミティブを変更しても、概念的な意味はありません。また、文字列を不変として扱うプログラムは、多くの場合、推論や理解が容易です。
さらに、あなたが見るように、Javaは実際に可変文字列と不変文字列の両方を提供しStringBuilder
ます。実際、デフォルトのみが不変の文字列です。StringBuilder
あらゆる場所に参照を渡したい場合は、完全に歓迎します。Javaは、型システムでの可変性またはその欠如を表現するためのサポートがないため、これらの概念に別個の型(String
およびStringBuilder
)を使用します。型システムで不変性をサポートしている言語(C ++などconst
)では、多くの場合、両方の目的に役立つ単一の文字列型があります。
はい、文字列を不変にすると、インターンなどの不変文字列に固有の最適化を実装でき、スレッド間で同期せずに文字列参照を渡すことができます。ただし、これにより、メカニズムが、言語の意図された目標と単純で一貫性のある型システムと混同されます。誰もがガベージコレクションを間違った方法で考える方法にこれを例えます。ガベージコレクションは「未使用のメモリの再生」ではありません。「無制限のメモリを搭載したコンピューターのシミュレーション」です。説明されているパフォーマンスの最適化は、不変文字列の目標を実際のマシンで適切に実行するために行われます。そのような文字列がそもそも不変である理由ではありません。
43 = 6
し、数43は数6と同じものを意味することを期待
i
42ではなく突然変異しましたstring s = "Hello "; s += "World";
。考慮してください。variableの値を変更しましたs
。しかし、文字列"Hello "
、"World"
および"Hello World"
不変です。
不変性とは、所有していないクラスが保持する定数を変更できないことを意味します。所有していないクラスにはJavaの実装のコアにあるクラスが含まれ、変更すべきでない文字列にはセキュリティトークン、サービスアドレスなどが含まれます。これらの種類を実際に変更することはできません物事(およびこれは、サンドボックスモードで動作するときに二重に適用されます)。
Stringが不変ではない場合、文字列の内容をその下で変更したくないコンテキストから取得するたびに、「念のため」コピーを取得する必要があります。それは非常に高価になります。
String
。しかし、たとえば、Array
sはそれでも変更可能です。それで、なぜString
不変であり、そうではArray
ないのか。そして、不変性が非常に重要な場合、Javaが不変オブジェクトの作成と操作をそれほど難しくするのはなぜですか?
一部のデータを受け入れ、その正当性を検証してから渡す(DBに保存するなど)システムを想像してください。
データがaでString
あり、少なくとも5文字の長さが必要であると仮定します。メソッドは次のようになります。
public void handle(String input) {
if (input.length() < 5) {
throw new IllegalArgumentException();
}
storeInDatabase(input);
}
storeInDatabase
ここでが呼び出されるとinput
、要件に適合することに同意できます。しかし、String
可変であれば、呼び出し側は、検証された直後にデータベースに保存される前に、input
オブジェクトを(別のスレッドから)変更できます。これには適切なタイミングが必要であり、おそらく毎回うまくいくとは限りませんが、時折、彼はデータベースに無効な値を保存することができます。
不変データ型は、この(および関連する多くの)問題に対する非常に単純な解決策です。値をチェックするときはいつでも、チェックされた条件が後で真であるという事実に依存することができます。
input
のhandle
方法は(どんなすでに長すぎるん元で input
ある)、それは単に例外をスローしていました。 メソッドを呼び出す前に新しい入力を作成しています。それは問題ではありません。
一般的に、値型と参照型に遭遇します。値型では、それを表すオブジェクトを気にせず、値を気にします。値を指定した場合、その値は同じままであると予想されます。突然変化することは望ましくありません。数字の5は値です。突然6に変わるとは思わないでしょう。文字列「Hello」は値です。突然「P *** off」に変わるとは思わないでしょう。
参照型は、オブジェクトを気に、あなたはそれを変更することが予想されます。たとえば、多くの場合、配列の変更が予想されます。私があなたに配列を与え、そしてあなたがそれを正確にそのままに保ちたいなら、あなたはそれを変更しないことを私に信頼しなければならないか、あなたはそれのコピーを作る。
Java文字列クラスでは、設計者は決定を行う必要がありました。文字列が値型のように振る舞うのか、参照型のように振る舞うのが良いのでしょうか。Java文字列の場合、それらは値型であることが決定されました。つまり、オブジェクトであるため、不変オブジェクトでなければなりません。
反対の決定を下すこともできたかもしれませんが、私の意見では、多くの頭痛の種になりました。他の場所で述べたように、多くの言語が同じ決定を下し、同じ結論に達しました。例外はC ++で、1つの文字列クラスがあり、文字列は定数または非定数にできますが、C ++では、Javaとは異なり、オブジェクトパラメータは参照としてではなく値として渡すことができます。
誰もこれを指摘していないことに本当に驚きました。
回答:たとえそれが可変であったとしても、それはあなたにとって大きな利益にはなりません。追加のトラブルを引き起こす限り、それはあなたに利益をもたらさないでしょう。突然変異の最も一般的な2つのケースを調べてみましょう。
Java文字列の各文字は2バイトまたは4バイトのいずれかを使用するため、自問してください。既存のコピーを変更できる場合、何か得られますか?
2バイト文字を4バイト文字(またはその逆)に置き換えるシナリオでは、文字列の残りの部分を2バイト左または右にシフトする必要があります。これは、計算の観点から文字列全体を完全にコピーすることと同じです。
これはまた、通常は望ましくない本当に不規則な動作です。誰かが英語のテキストを使用してアプリケーションをテストし、そのアプリケーションが中国などの外国に採用されると、全体が奇妙に動作し始めると想像してください。
2つの任意の文字列がある場合、それらは2つの異なるメモリ位置に配置されます。2番目の文字列を追加して最初の文字列を変更する場合、最初の文字列が既に使用されている可能性があるため、最初の文字列の最後に追加メモリを要求することはできません。
連結された文字列をまったく新しい場所にコピーする必要があります。これは、両方の文字列が不変である場合とまったく同じです。
効率的に追加を行いたい場合StringBuilder
は、将来の追加のために、文字列の末尾にかなりの量のスペースを予約するを使用できます。
それらは高価であり、不変に保つことで、メイン文字列のバイト配列を共有するサブ文字列などが可能になります。(新しいバイト配列を作成してコピーする必要がないので、速度が向上します)
セキュリティ-パッケージまたはクラスコードの名前を変更したくない
[StringBuilder srcで見た古い3を削除-文字列とメモリを共有しません(変更されるまで)1.3または1.4だったと思います]
キャッシュハッシュコード
複数の文字列にはSB(必要に応じてビルダーまたはバッファー)を使用します
文字列はJavaのプリミティブデータ型である必要がありました。もしそうであれば、文字列はデフォルトで可変になり、最終キーワードは不変文字列を生成します。可変文字列は便利なので、stringbuffer、stringbuilder、charsequenceクラスの可変文字列には複数のハックがあります。