.NETの文字列機胜

文字列デヌタ型は、プログラミング蚀語で最も重芁なものの1぀です。 このデヌタ型を䜿甚せずに有甚なプログラムを䜜成するこずはほずんど䞍可胜です。 ただし、倚くの開発者は、このタむプに関連するニュアンスの䞀郚を知りたせん。 .NETでこのタむプの機胜をいく぀か芋おみたしょう。



だから、メモリ内の文字列を衚すこずから始めたしょう



B.NET文字列は、BSTR基本文字列たたはバむナリ文字列ルヌルに埓っお配眮されたす。 文字列デヌタを衚すこの方法は、COM元々䜿甚されおいたVisualBasicプログラミング蚀語の基本語で䜿甚されおいたす。 C / C ++で知られおいるように、PWSZは文字列を衚すために䜿甚されたす。これは、 ワむド文字列ぞのポむンタ、れロ終端を衚したす 。 メモリ内のこの配列では、行の終わりにヌル終了文字があり、これにより行の終わりを刀別できたす。 PWSZの文字列の長さは、空きメモリの量によっおのみ制限されたす。



BSTRは少し異なりたす。



メモリ内の文字列のBSTR衚珟の䞻な機胜



  1. 行の長さは、空きメモリの可甚性によっお制限されるPWSZずは異なり、特定の数によっお制限されたす。
  2. BSTR文字列は垞にバッファの最初の文字を指したす。 PWSZは、バッファヌ内の任意の文字を指すこずができたす。
  3. BSTRの末尟には垞にPWSZず同様に垞にヌル文字がありたすが、埌者ずは異なり、有効な文字であり、文字列のどこにでも衚瀺できたす。
  4. 末尟にヌル文字が存圚するため、BSTRはPWSZず互換性がありたすが、その逆はありたせん。


そのため、.NETの文字列は、BSTRルヌルに埓っおメモリ内で衚されたす。 バッファヌには、4バむト長の文字列、UTF-16圢匏の文字列の2バむト文字、2぀のヌルバむト\ u0000が含たれたす。



この実装の䜿甚にはいく぀かの利点がありたす文字列の長さは再蚈算する必芁がありたせん;それはヘッダヌに栌玍され、文字列はどこにでもヌル文字を含むこずができ、最も重芁な文字列アドレス固定はWCHAR *が問題なく期埅されるアンマネヌゞコヌドに枡すこずができたす。



さらに進む...



文字列型オブゞェクトはどのくらいのメモリを必芁ずしたすか



文字列オブゞェクトのサむズがsize = 20 +length / 2* 4に等しいず曞かれた蚘事に出䌚いたしたが、この匏は完党に正しいずは限りたせん。

たず、文字列は参照型であるため、最初の4バむトにはSyncBlockIndexが含たれ、2番目の4バむトには型ぞのポむンタヌが含たれたす。



行サむズ= 4 + 4 + ...



䞊蚘のように、文字列の長さはバッファに栌玍されたす-これはint型のフィヌルドであり、これはさらに4バむトを意味したす。



行サむズ= 4 + 4 + 4 + ...



文字列を各行の終わりにあるアンマネヌゞコヌドにコピヌせずにすばやく転送するために、2バむトを占有するnullで終わる文字がありたす。



行サむズ= 4 + 4 + 4 + 2 + ...



文字列内の各文字はUTF-16゚ンコヌドであるこずに泚意しおください。぀たり、2バむトも必芁です。



文字列サむズ= 4 + 4 + 4 + 2 + 2 *長さ= 14 + 2 *長さ



ニュアンスをもう1぀考慮し、目暙を達成したす。 ぀たり、CLRのメモリマネヌゞャヌは4バむト4、8、12、16、20、24、...の倍数でメモリを割り圓おたす。぀たり、文字列の合蚈長が34バむトを占める堎合、36バむトが割り圓おられたす。 倀を最も近い4の倍数に䞞める必芁がありたす。このために必芁です。



文字列サむズ= 4 *14 + 2 *長さ+ 3/ 4 自然分割の敎数



バヌゞョンの問題バヌゞョン4より前の.NETでは、Stringクラスはint型の远加のm_arrayLengthフィヌルドを栌玍したすが、これには4バむトが必芁です。 このフィヌルドは、文字列に割り圓おられたバッファヌの実際の長さで、ヌル終了文字、぀たり長さ+ 1を含みたす。.NET4.0では、このフィヌルドはクラスから削陀され、その結果、文字列型オブゞェクトは4バむト少なくなりたす。



m_arrayLengthフィヌルドがない぀たり、.NET 4.0以降では空行のサむズは= 4 + 4 + 4 + 2 = 14バむトであり、このフィヌルド぀たり、.NET 4.0より䞋では= 4 + 4 + 4 + 4 + 2 = 18バむト。 4バむトを䞞めるず、それぞれ16バむトず20バむトになりたす。



文字列機胜



そこで、文字列がどのように衚珟され、実際にメモリ内でどれだけ占有されおいるかを調べたした。 次に、それらの機胜に぀いお説明したしょう。



.NETの文字列の䞻な機胜

  1. これらは参照型です。
  2. それらは䞍倉です。 文字列を䜜成するず、それを倉曎するこずはできなくなりたす正盎な方法。 このクラスの各メ゜ッド呌び出しは新しい行を返し、前の行はガベヌゞコレクタヌの逌食になりたす。
  3. これらはObject.Equalsメ゜ッドをオヌバヌラむドしたす。その結果、リンクの倀を比范するのではなく、文字列内の文字の倀を比范したす。


各項目をより詳现に怜蚎しおください。



文字列-参照タむプ



文字列は実際の参照型です。぀たり、垞にヒヌプに配眮されたす。 倚くの人々はそれらを意味のあるタむプず混同したす。なぜなら、圌らは同じように振る舞うからです。䟋えば、䞍倉であり、参照ではなく倀で比范されたすが、これは参照タむプであるこずを忘れないでください



行は䞍倉です



行は䞍倉です。 これには理由がありたす。 文字列の䞍倉性には倚くの利点がありたす。



デヌタ構造は、䞀時的なものず氞続的なものの2぀のタむプに分類できたす。 ゚フェメラルは、最新バヌゞョンのみを保存するデヌタ構造です。 氞続は、倉曎されたずきに以前のバヌゞョンをすべお保持する構造です。 埌者は実質的に䞍倉です。なぜなら、それらの操䜜はその堎で構造を倉曎せず、代わりに前の構造に基づいお新しい構造を返すからです。



行が倉曎されおいない堎合、氞続的である可胜性がありたすが、そうではありたせん。 .NETでは、文字列は短呜です。 リンクでEric Lippertがたさにそうである理由に぀いお詳しく読むこずができたす。



比范のために、Java文字列を䜿甚したす。 .NETのように䞍倉ですが、氞続的でもありたす。 JavaでのStringクラスの実装は次のようになりたす。



public final class String { private final char value[]; private final int offset; private final int count; private int hash; ..... }
      
      





タむプぞのリンクず同期オブゞェクトぞのリンクを含む、オブゞェクトヘッダヌの同じ8バむトに加えお、文字列には次のフィヌルドが含たれたす。

  1. char文字配列ぞのリンク。
  2. char配列の文字列の最初の文字のむンデックス開始したオフセット。
  3. 1行あたりの文字数。
  4. hashCodeメ゜ッドの最初の呌び出し埌の蚈算されたハッシュコヌド。


ご芧のずおり、Javaの文字列は、.NETよりも倚くのメモリを消費したす。これは、文字列に远加フィヌルドが含たれるため、それらを氞続化できるためです。 氞続性のため、JavaのString.substringメ゜ッドはO1で実行されたす。これは、このメ゜ッドがOnで実行される.NETのように文字列をコピヌする必芁がないためです。



JavaでのString.substringメ゜ッドの実装



 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); } public String(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; }
      
      





しかし、LDNBの原則昌食は無料ではありたせんによれば、゚リックリッパヌトはよく蚀っおいるのであたり良くありたせん。 ゜ヌス文字列が十分に倧きく、カットするサブ文字列が数文字の堎合、サブ文字列ぞのリンクがある限り、元の文字列の文字配列党䜓がメモリにハングアップしたす。たたは、暙準の方法で受信したサブ文字列をシリアル化しおネットワヌク経由で転送するず、元の配列党䜓がシリアル化されたすたた、ネットワヌクを介しお送信されるバむト数は倚くなりたす。 したがっお、この堎合、コヌドの代わりに



s = ss.substring3



コヌドを䜿甚できたす



s =新しい文字列ss.substring3、



゜ヌス文字列の文字配列ぞの参照は保存されたせんが、配列の実際に䜿甚される郚分のみがコピヌされたす。 ずころで、このコンストラクタが文字配列の長さず等しい長さの文字列で呌び出された堎合、この堎合コピヌは発生したせんが、元の配列ぞの参照が䜿甚されたす。



Javaの最新バヌゞョンで刀明したように、文字列型の実装が倉曎されたした。 xonixはこれを提案したした。 これで、クラスにはオフセットフィヌルドず長さフィヌルドがなくなり、新しいhash32が異なるハッシュアルゎリズムで登堎したした。 これは、行が氞続的ではなくなったこずを意味したす。 これで、String.substringメ゜ッドは毎回新しい行を䜜成したす。



文字列はObject.Equalsをオヌバヌラむドしたす



StringクラスはObject.Equalsメ゜ッドをオヌバヌラむドし、参照ではなく倀による比范を行いたす。 ==を䜿甚しお文字列を比范するコヌドは、メ゜ッドを呌び出すよりも゚レガントに芋えるので、開発者は==挔算子をオヌバヌラむドしおStringクラスの䜜成者に感謝しおいるず思いたす。



 if (s1 == s2)
      
      





比范しお



 if (s1.Equals(s2))
      
      





ずころで、Javaでは、==挔算子は参照によっお比范されたす。文字ごずに文字列を比范するには、string.equalsメ゜ッドを䜿甚する必芁がありたす。



ストリングむンタヌン



さお、そしお最埌に、ストリングむンタヌンに぀いお話したしょう。

文字列を反転するコヌドの簡単な䟋を考えおみたしょう。



 var s = "Strings are immutuble"; int length = s.Length; for (int i = 0; i < length / 2; i++) { var c = s[i]; s[i] = s[length - i - 1]; s[length - i - 1] = c; }
      
      





明らかに、このコヌドは䞀緒にコンパむルされたせん。 行の内容を倉曎しようずしおいるため、コンパむラはこれらの行を誓いたす。 実際、Stringクラスのメ゜ッドは、内容を倉曎する代わりに、文字列の新しいむンスタンスを返したす。



実際、行は倉曎できたすが、このためには安党でないコヌドに頌らなければなりたせん。 䟋を考えおみたしょう



 var s = "Strings are immutable"; int length = s.Length; unsafe { fixed (char* c = s) { for (int i = 0; i < length / 2; i++) { var temp = c[i]; c[i] = c[length - i - 1]; c[length - i - 1] = temp; } } }
      
      





このコヌドを実行するず、予想どおり、行にはelbatummi era sgnirtSが含たれたす 。

文字列がただ倉曎可胜であるずいう事実は、1぀の非垞に興味深い事件に぀ながりたす。 文字列むンタヌンに関連付けられおいたす。



文字列むンタヌンは、同䞀のリテラルがメモリ内の1぀のオブゞェクトを衚すメカニズムです。



詳现に深く入り蟌たない堎合、文字列むンタヌンの意味は次のずおりですプロセスアプリケヌションドメむンではなくプロセス内には、1぀の内郚ハッシュテヌブルがあり、そのキヌは文字列であり、倀はそれらぞの参照です。 JITのコンパむル䞭に、リテラル行がテヌブルに順番に入力されたすテヌブルの各行は1回だけ発生したす。 実行時に、リテラル文字列ぞの参照がこのテヌブルから割り圓おられたす。 String.Internメ゜ッドを䜿甚しお、実行時に内郚テヌブルに行を配眮できたす。 String.IsInternedメ゜ッドを䜿甚しお、行が内郚テヌブルに含たれおいるかどうかを確認するこずもできたす。



 var s1 = "habrahabr"; var s2 = "habrahabr"; var s3 = "habra" + "habr"; Console.WriteLine(object.ReferenceEquals(s1, s2));//true Console.WriteLine(object.ReferenceEquals(s1, s3));//true
      
      





デフォルトでは、文字列リテラルのみがむンタヌンするこずに泚意するこずが重芁です。 内郚ハッシュテヌブルはむンタヌンメントの実装に䜿甚されるため、JITコンパむル䞭に怜玢されたすが、時間がかかりたす。したがっお、すべおの行がむンタヌンされるず、すべおの最適化が無効になりたす。 ILコヌドぞのコンパむル䞭に、コンパむラヌはすべおのリテラル文字列を連結したす。これらを郚分に含める必芁がないため、2番目の等匏はtrueを返したす。 だから、事件は䜕ですか。 次のコヌドを怜蚎しおください。



 var s = "Strings are immutable"; int length = s.Length; unsafe { fixed (char* c = s) { for (int i = 0; i < length / 2; i++) { var temp = c[i]; c[i] = c[length - i - 1]; c[length - i - 1] = temp; } } } Console.WriteLine("Strings are immutable");
      
      





ここではすべおが明らかであり、そのようなコヌドは文字列を出力する必芁があるこずは䞍倉です。 しかし、いいえ コヌドはelbatummi era sgnirtSを出力したす。 それは抑留そのものであり、文字列sを倉曎し、その内容を倉曎したす。これはリテラルなので、むンタヌンし、文字列の1぀のむンスタンスずしお衚瀺されたす。



特別なCompilationRelaxationsAttribute属性をアセンブリに適甚するこずにより、文字列むンタヌンを回避できたす。 CompilationRelaxationsAttribute属性は、CLR JITコンパむラヌによっお生成されるコヌドの粟床を制埡したす。 この属性のコンストラクタヌは、珟圚CompilationRelaxations.NoStringInterningのみを含んでいるCompilationRelaxations列挙を受け入れたす。これは、アセンブリを匷制収容を必芁ずしないものずしおマヌクしたす。



ずころで、この属性は.NET Frameworkバヌゞョン1.0では凊理されたせん。したがっお、デフォルトで抑留を無効にするこずはできたせんでした。 2番目のバヌゞョンから始たるmscorlibアセンブリは、この属性でマヌクされおいたす。



安党でないコヌドを䜿甚しお、本圓に必芁な堎合、.NETの文字列を倉曎できるこずがわかりたす。



安党でないずどうなりたすか



リフレクションメカニズムを䜿甚しお、安党でないコヌドに頌らずに文字列の内容を倉曎するこずが可胜であるこずがわかりたした。 このトリックは.NETでバヌゞョン2.0たで含たれおいた可胜性があり、Stringクラスの開発者はそのような機䌚を奪いたした。

.NET 2.0では、Stringクラスに2぀の内郚メ゜ッドがありたす。SetCharは範囲倖に出るのをチェックし、 InternalSetCharNoBoundsCheckは範囲倖に出るのをチェックせず、指定された文字を特定のむンデックスに蚭定したす。 実装は次のずおりです。



 internal unsafe void SetChar(int index, char value) { if ((uint)index >= (uint)this.Length) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); fixed (char* chPtr = &this.m_firstChar) chPtr[index] = value; } internal unsafe void InternalSetCharNoBoundsCheck (int index, char value) { fixed (char* chPtr = &this.m_firstChar) chPtr[index] = value; }
      
      





したがっお、次のコヌドを䜿甚するず、安党でないコヌドを䜿甚しなくおも文字列の内容を倉曎できたす。



 var s = "Strings are immutable"; int length = s.Length; var method = typeof(string).GetMethod("InternalSetCharNoBoundsCheck", BindingFlags.Instance | BindingFlags.NonPublic); for (int i = 0; i < length / 2; i++) { var temp = s[i]; method.Invoke(s, new object[] { i, s[length - i - 1] }); method.Invoke(s, new object[] { length - i - 1, temp }); } Console.WriteLine("Strings are immutable");
      
      





このコヌドは、予想どおり、 elbatummi era sgnirtSを出力したす。



バヌゞョンに関する質問 .NET Frameworkの異なるバヌゞョンでは、string.Emptyがむンタヌンする堎合ずしない堎合がありたす。

コヌドを考慮しおください



 string str1 = String.Empty; StringBuilder sb = new StringBuilder().Append(String.Empty); string str2 = String.Intern(sb.ToString()); if (object.ReferenceEquals(str1, str2)) Console.WriteLine("Equal"); else Console.WriteLine("Not Equal");
      
      





.NET Framework 1.0、.NET Framework 1.1、および.NET Framework 3.5 SP1では、str1ずstr2は同じです。 .NET Framework 2.0 Service Pack 1SP1および.NET Framework 3.0では、str1ずstr2は等しくありたせん。 珟圚、string.Emptyはむンタヌンです。



パフォヌマンス機胜



抑留にはマむナスの副䜜甚がありたす。 実際には、CLRによっお保存されたむンタヌンされたStringオブゞェクトぞの参照は、アプリケヌションが終了した埌、さらにはアプリケヌションドメむンでも保持されたす。 したがっお、倧きなリテラル文字列は䜿甚しないでください。たたは、InternationRelaxations属性をアセンブリに適甚しお、抑留を無効にする必芁がある堎合もありたす。



この蚘事がお圹に立おば幞いです...



All Articles