ここでは、文字列内の指定した文字列を別の文字列に置換する方法(例えば、「昨日は雨でした。」の「雨」を「晴れ」に置換して、「昨日は晴れでした。」という文字列を作成する方法)を幾つか紹介します。
最も一般的な方法は、String.Replaceメソッドを使う方法です。String.Replaceメソッドは、1番目のパラメータに検索する文字列を、2番目のパラメータに置換後の文字列を指定します。するとこのメソッドは、置換された文字列を返します。
見つかった文字列はすべて置換されます。はじめに見つかった文字列だけを置換するといったことはできません。
文字列を検索する方法は、序数による検索のみです。よって、全く同じ文字列しか検索できません。大文字小文字を区別しない検索や、カルチャに依存した検索はできません。
String.Replaceメソッドは置換された結果の文字列を返しますが、基のStringの内容は変更されないことに注意してください。
String.Replaceメソッドには、パラメータがChar型のオーバーロードもあります。この方が高速ですので、1文字だけ置換する場合はパラメータにChar型を指定すべきです。
以下にString.Replaceメソッドを使用した例を幾つか示します。
Dim s As String = "にわにはにわにわとりがいる。" '文字列を置換する(「にわ」を「庭」に置換する) Dim r1 As String = s.Replace("にわ", "庭") '庭には庭庭とりがいる。 '文字を置換する(「に」を「2」に置換する) Dim r2 As String = s.Replace("に"c, "2"c) '2わ2は2わ2わとりがいる。
string s = "にわにはにわにわとりがいる。"; //文字列を置換する(「にわ」を「庭」に置換する) string r1 = s.Replace("にわ", "庭"); //庭には庭庭とりがいる。 //文字を置換する(「に」を「2」に置換する) string r2 = s.Replace('に', '2'); //2わ2は2わ2わとりがいる。
StringBuilderクラスの場合は、StringBuilder.Replaceメソッドで文字列を置換することができます。使い方や動作はString.Replaceメソッドとほぼ同様で、序数による検索で、見つかったすべての文字列を置換します。また、Char型パラメータのオーバーロードも用意されています。
String.Replaceと違うのは、StringBuilder.ReplaceメソッドはStringBuilderオブジェクトの内容そのものが変更されることです。また、StringBuilder.Replaceメソッドでは、文字列を検索する範囲を指定することもできます。
なおString.ReplaceとStringBuilder.Replaceメソッドのパフォーマンスの比較については、後で詳しく説明します。
以下にStringBuilder.Replaceメソッドを使用した例を示します。
Dim s As String = "にわにはにわにわとりがいる。" 'StringBuilderを作成する Dim sb As New System.Text.StringBuilder(s) '文字列を置換する(「にわとり」を「鶏」に置換する) sb.Replace("にわとり", "鶏") '先頭から2文字の範囲で、文字列を置換する(先頭の「にわ」を「庭」に置換する) sb.Replace("にわ", "庭", 0, 2) 'Stringに変換する Dim r1 As String = sb.ToString() '庭にはにわ鶏がいる。
string s = "にわにはにわにわとりがいる。"; //StringBuilderを作成する System.Text.StringBuilder sb = new System.Text.StringBuilder(s); //文字列を置換する(「にわとり」を「鶏」に置換する) sb.Replace("にわとり", "鶏"); //先頭から2文字の範囲で、文字列を置換する(先頭の「にわ」を「庭」に置換する) sb.Replace("にわ", "庭", 0, 2); //Stringに変換する string r1 = sb.ToString(); //庭にはにわ鶏がいる。
VB.NETのReplace関数を使えば、置換する回数を指定することができます。また、テキストモードでの検索もできます。テキストモードでは、大文字と小文字、ひらがなとカタカナ、半角と全角を区別しないで検索し、置換します。
以下にVB.NETのReplace関数を使った例を幾つか示します。
'Imports Microsoft.VisualBasic Dim s As String = "にわにはにわにわとりがいる。" '2回だけ置換する Dim r1 As String = Replace(s, "にわ", "庭", 1, 2, CompareMethod.Binary) '庭には庭にわとりがいる。 'ひらがなとカタカナを区別しないで、すべての"ニワ"を"庭"に置換する Dim r2 As String = Replace(s, "ニワ", "庭", 1, -1, CompareMethod.Text) '庭には庭庭とりがいる。 '3文字目以降の部分文字列を、1回だけ置換する Dim r3 As String = Replace(s, "にわ", "2羽", 3, 1, CompareMethod.Binary) 'には2羽にわとりがいる。
//参照設定に「Microsoft.VisualBasic」を追加する必要がある string s = "にわにはにわにわとりがいる。"; //2回だけ置換する string r1 = Microsoft.VisualBasic.Strings.Replace(s, "にわ", "庭", 1, 2, Microsoft.VisualBasic.CompareMethod.Binary); //庭には庭にわとりがいる。 //ひらがなとカタカナを区別しないで、すべての"ニワ"を"庭"に置換する string r2 = Microsoft.VisualBasic.Strings.Replace(s, "ニワ", "庭", 1, -1, Microsoft.VisualBasic.CompareMethod.Text); //庭には庭庭とりがいる。 //3文字目以降の部分文字列を、1回だけ置換する string r3 = Microsoft.VisualBasic.Strings.Replace(s, "にわ", "2羽", 3, 1, Microsoft.VisualBasic.CompareMethod.Binary); //には2羽にわとりがいる。
Regex.Replaceメソッドを使うと、正規表現を使って文字列の置換を行うことができます。正規表現を使って置換を行うと、String.ReplaceやStringBuilder.Replaceメソッドではできない、複雑な置換を行うことができます。ただし、正規表現についての知識が必要ですし、パフォーマンスは、通常、String.ReplaceやStringBuilder.Replaceメソッドと比べて大きく劣ります。
正規表現の置換について詳しくは、「正規表現を使って文字列を置換する」をご覧ください。
大文字と小文字を区別しない置換や、置換する回数の指定は、VB.NETのReplaceやRegex.Replaceを使えばできます。しかしこれらを使わなくても、自分でそのようなメソッドを作成すれば、意外と簡単にできます。
以下に、置換する回数の指定と、大文字と小文字を区別しない置換が可能なメソッドの例を紹介します。
''' <summary> ''' 指定した文字列内の指定した文字列を別の文字列に置換する。 ''' </summary> ''' <param name="input">置換する文字列のある文字列。</param> ''' <param name="oldValue">検索文字列。</param> ''' <param name="newValue">置換文字列。</param> ''' <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param> ''' <param name="compInfo">文字列の検索に使用するCompareInfo。</param> ''' <param name="compOptions">文字列の検索に使用するCompareOptions。</param> ''' <returns>置換された結果の文字列。</returns> Public Shared Function StringReplace(ByVal input As String, ByVal oldValue As String, _ ByVal newValue As String, ByVal count As Integer, _ ByVal compInfo As System.Globalization.CompareInfo, _ ByVal compOptions As System.Globalization.CompareOptions) As String If input Is Nothing OrElse input.Length = 0 OrElse _ oldValue Is Nothing OrElse oldValue.Length = 0 OrElse _ count = 0 Then Return input End If If compInfo Is Nothing Then compInfo = System.Globalization.CultureInfo.InvariantCulture.CompareInfo compOptions = System.Globalization.CompareOptions.Ordinal End If Dim inputLen As Integer = input.Length Dim oldValueLen As Integer = oldValue.Length Dim buf As New System.Text.StringBuilder(inputLen) Dim currentPoint As Integer = 0 Dim foundPoint As Integer = -1 Dim currentCount As Integer = 0 Do '文字列を検索する foundPoint = compInfo.IndexOf(input, oldValue, currentPoint, compOptions) If foundPoint < 0 Then buf.Append(input.Substring(currentPoint)) Exit Do End If '見つかった文字列を新しい文字列に換える buf.Append(input.Substring(currentPoint, foundPoint - currentPoint)) buf.Append(newValue) '次の検索開始位置を取得 currentPoint = foundPoint + oldValueLen '指定回数置換したか調べる currentCount += 1 If currentCount = count Then buf.Append(input.Substring(currentPoint)) Exit Do End If Loop While currentPoint < inputLen Return buf.ToString() End Function ''' <summary> ''' 指定した文字列内の指定した文字列を別の文字列に置換する。 ''' </summary> ''' <param name="input">置換する文字列のある文字列。</param> ''' <param name="oldValue">検索文字列。</param> ''' <param name="newValue">置換文字列。</param> ''' <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param> ''' <param name="ignoreCase">大文字と小文字を区別しない時はTrue。</param> ''' <returns>置換された結果の文字列。</returns> Public Shared Function StringReplace(ByVal input As String, ByVal oldValue As String, _ ByVal newValue As String, ByVal count As Integer, _ ByVal ignoreCase As Boolean) As String If ignoreCase Then Return StringReplace(input, oldValue, newValue, count, _ System.Globalization.CultureInfo.InvariantCulture.CompareInfo, _ System.Globalization.CompareOptions.OrdinalIgnoreCase) Else Return StringReplace(input, oldValue, newValue, count, _ System.Globalization.CultureInfo.InvariantCulture.CompareInfo, _ System.Globalization.CompareOptions.Ordinal) End If End Function ''' <summary> ''' 指定した文字列内の指定した文字列を別の文字列に置換する。 ''' </summary> ''' <param name="input">置換する文字列のある文字列。</param> ''' <param name="oldValue">検索文字列。</param> ''' <param name="newValue">置換文字列。</param> ''' <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param> ''' <returns>置換された結果の文字列。</returns> Public Shared Function StringReplace(ByVal input As String, ByVal oldValue As String, _ ByVal newValue As String, ByVal count As Integer) As String Return StringReplace(input, oldValue, newValue, count, _ System.Globalization.CultureInfo.InvariantCulture.CompareInfo, _ System.Globalization.CompareOptions.Ordinal) End Function
/// <summary> /// 指定した文字列内の指定した文字列を別の文字列に置換する。 /// </summary> /// <param name="input">置換する文字列のある文字列。</param> /// <param name="oldValue">検索文字列。</param> /// <param name="newValue">置換文字列。</param> /// <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param> /// <param name="compInfo">文字列の検索に使用するCompareInfo。</param> /// <param name="compOptions">文字列の検索に使用するCompareOptions。</param> /// <returns>置換された結果の文字列。</returns> public static string StringReplace( string input, string oldValue, string newValue, int count, System.Globalization.CompareInfo compInfo, System.Globalization.CompareOptions compOptions) { if (input == null || input.Length == 0 || oldValue == null || oldValue.Length == 0 || count == 0) { return input; } if (compInfo == null) { compInfo = System.Globalization.CultureInfo.InvariantCulture.CompareInfo; compOptions = System.Globalization.CompareOptions.Ordinal; } int inputLen = input.Length; int oldValueLen = oldValue.Length; System.Text.StringBuilder buf = new System.Text.StringBuilder(inputLen); int currentPoint = 0; int foundPoint = -1; int currentCount = 0; do { //文字列を検索する foundPoint = compInfo.IndexOf(input, oldValue, currentPoint, compOptions); if (foundPoint < 0) { buf.Append(input.Substring(currentPoint)); break; } //見つかった文字列を新しい文字列に換える buf.Append(input.Substring(currentPoint, foundPoint - currentPoint)); buf.Append(newValue); //次の検索開始位置を取得 currentPoint = foundPoint + oldValueLen; //指定回数置換したか調べる currentCount++; if (currentCount == count) { buf.Append(input.Substring(currentPoint)); break; } } while (currentPoint < inputLen); return buf.ToString(); } /// <summary> /// 指定した文字列内の指定した文字列を別の文字列に置換する。 /// </summary> /// <param name="input">置換する文字列のある文字列。</param> /// <param name="oldValue">検索文字列。</param> /// <param name="newValue">置換文字列。</param> /// <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param> /// <param name="ignoreCase">大文字と小文字を区別しない時はTrue。</param> /// <returns>置換された結果の文字列。</returns> public static string StringReplace( string input, string oldValue, string newValue, int count, bool ignoreCase) { if (ignoreCase) { return StringReplace(input, oldValue, newValue, count, System.Globalization.CultureInfo.InvariantCulture.CompareInfo, System.Globalization.CompareOptions.OrdinalIgnoreCase); } else { return StringReplace(input, oldValue, newValue, count, System.Globalization.CultureInfo.InvariantCulture.CompareInfo, System.Globalization.CompareOptions.Ordinal); } } /// <summary> /// 指定した文字列内の指定した文字列を別の文字列に置換する。 /// </summary> /// <param name="input">置換する文字列のある文字列。</param> /// <param name="oldValue">検索文字列。</param> /// <param name="newValue">置換文字列。</param> /// <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param> /// <returns>置換された結果の文字列。</returns> public static string StringReplace( string input, string oldValue, string newValue, int count) { return StringReplace(input, oldValue, newValue, count, System.Globalization.CultureInfo.InvariantCulture.CompareInfo, System.Globalization.CompareOptions.Ordinal); }
このメソッドの使い方は、以下のような感じです。
Dim s As String = "Niwanihaniwaniwatorigairu." '1回だけ置換する Dim r1 As String = StringReplace(s, "niwa", "庭", 1) 'Niwaniha庭niwatorigairu. '大文字と小文字を区別しないで、すべて置換する Dim r2 As String = StringReplace(s, "niwa", "庭", -1, True) '庭niha庭庭torigairu.
string s = "Niwanihaniwaniwatorigairu."; //1回だけ置換する string r1 = StringReplace(s, "niwa", "庭", 1); //Niwaniha庭niwatorigairu. //大文字と小文字を区別しないで、すべて置換する string r2 = StringReplace(s, "niwa", "庭", -1, true); //庭niha庭庭torigairu.
通常の文字列置換は、StringクラスのReplaceメソッドで十分です。それではどのようなケースでStringBuilderクラスを使うのでしょうか?
基本的には、「文字列処理を高速に行う」で説明しているように、同じ文字列に対して何回も置換を行う時は、StringBuilderを使います。
しかし、「StringBuilder vs. String / Fast String Operations with .NET 2.0」や「Comparing RegEx.Replace, String.Replace and StringBuilder.Replace - Which has better performance?」などによると、同じ文字列に対して複数回置換を行う場合であっても、String.Replaceの方がStringBuilder.Replaceよりも若干速いというベンチマークの結果が出ています。
私も以下のようなコードで、文字列の長さを変えたり、置換する回数を変えたりして試してみましたが、ほとんどの場合でString.Replaceの方が若干速いようでした。
Dim builder As New System.Text.StringBuilder() For i As Integer = 0 To 20000 builder.Append("01234") Next Dim s As String = builder.ToString() Dim sw1 As New System.Diagnostics.Stopwatch() sw1.Start() For i As Integer = 0 To 1000 s = s.Replace((i Mod 10).ToString(), ((i + 5) Mod 10).ToString()) Next sw1.Stop() Console.WriteLine("String.Replace: {0}", sw1.ElapsedTicks) Dim sw2 As New System.Diagnostics.Stopwatch() sw2.Start() Dim sb As New System.Text.StringBuilder(s) For i As Integer = 0 To 1000 sb.Replace((i Mod 10).ToString(), ((i + 5) Mod 10).ToString()) Next s = sb.ToString() sw2.Stop() Console.WriteLine("StringBuild.Replace: {0}", sw2.ElapsedTicks) Dim sw3 As New System.Diagnostics.Stopwatch() sw3.Start() For i As Integer = 0 To 1000 s = Replace(s, (i Mod 10).ToString(), ((i + 5) Mod 10).ToString()) Next sw3.Stop() Console.WriteLine("VB.NET Strings.Replace: {0}", sw3.ElapsedTicks) Dim sw4 As New System.Diagnostics.Stopwatch() sw4.Start() For i As Integer = 0 To 1000 s = System.Text.RegularExpressions.Regex.Replace( _ s, (i Mod 10).ToString(), ((i + 5) Mod 10).ToString()) Next sw4.Stop() Console.WriteLine("Regex.Replace: {0}", sw4.ElapsedTicks) 'String.Replace: 2186982 'StringBuild.Replace: 3461453 'VB.NET Strings.Replace: 6677110 'Regex.Replace: 18180959
System.Text.StringBuilder builder = new System.Text.StringBuilder(); for (int i = 0; i < 20000; i++) { builder.Append("01234"); } string s = builder.ToString(); System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch(); sw1.Start(); for (int i = 0; i < 1000; i++) { s = s.Replace((i % 10).ToString(), ((i + 5) % 10).ToString()); } sw1.Stop(); Console.WriteLine("String.Replace: {0}", sw1.ElapsedTicks); System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); sw2.Start(); System.Text.StringBuilder sb = new System.Text.StringBuilder(s); for (int i = 0; i < 1000; i++) { sb.Replace((i % 10).ToString(), ((i + 5) % 10).ToString()); } s = sb.ToString(); sw2.Stop(); Console.WriteLine("StringBuild.Replace: {0}", sw2.ElapsedTicks); System.Diagnostics.Stopwatch sw3 = new System.Diagnostics.Stopwatch(); sw3.Start(); for (int i = 0; i < 1000; i++) { s = Microsoft.VisualBasic.Strings.Replace( s, (i % 10).ToString(), ((i + 5) % 10).ToString()); } sw3.Stop(); Console.WriteLine("VB.NET Strings.Replace: {0}", sw3.ElapsedTicks); System.Diagnostics.Stopwatch sw4 = new System.Diagnostics.Stopwatch(); sw4.Start(); for (int i = 0; i < 1000; i++) { s = System.Text.RegularExpressions.Regex.Replace( s, (i % 10).ToString(), ((i + 5) % 10).ToString()); } sw4.Stop(); Console.WriteLine("Regex.Replace: {0}", sw4.ElapsedTicks); //String.Replace: 2188440 //StringBuild.Replace: 3446613 //VB.NET Strings.Replace: 6839046 //Regex.Replace: 18224702
ちなみにChar型の置換について上と同様の方法でString.ReplaceとStringBuild.Replaceを比較してみたところ、この場合はStringBuildの方が速いという結果になりました。
なおこれらの結果は環境によって変化する可能性があるため一概には言えないことをご了承ください。