文字列操作関連のクラス

注意:
この文書は以前「.NETでいきまっしょい!」で公開していたものですが、公開以降メンテナンスされていません。 今や古い情報となった内容が記載されている場合があるのでご注意ください。

1.StringクラスとStringBuilderクラス


 VB.NETなどの元となった言語であるBASICには、プリミティブ型として文字列型が存在していました。 この文字列型は他の言語にはない使いやすさがあり、多少の仕様変更を伴いながらもBASICからQucik BASIC、Visual Basicと変わることなく受け継がれてきました。 Visual Basic .NETでももちろん文字列型を扱うことができますが、VBの文字列型と比較すると格段に機能が向上しています。 というのも、文字列に関する処理をまとめ直し、VB.NETでは文字列型も一つのクラス型として存在するようになったからです。
 次のコードが示すように、String型は.NET FrameworkにおけるSystem.String型のエイリアス名で、両者は全く同じものです。 また、文字列型はクラスであるため参照型となります。 さらに、文字列型がクラス型となったため様々なメソッドを持つようになりました。
String型とそのメソッド
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
Module Module1

    Sub Main()

        ' VB.NET の String型は .NET Framework の System.Stringクラスのエイリアス名
        Dim s1 As String
        Dim s2 As System.String

        ' s1に文字列を代入
        s1 = "sample string"

        ' s1の参照を代入
        s2 = s1

        Console.WriteLine("s1: {0}, s2: {1}, s1 is s2: {2}", s1, s2, s1 Is s2)

        ' s1のコピーへの参照を代入
        s2 = String.Copy(s1)

        Console.WriteLine("s1: {0}, s2: {1}, s1 is s2: {2}", s1, s2, s1 Is s2)

        ' 文字列の連結
        s1 += " combined"
        ' s1 = s1 + " combined"

        Console.WriteLine(s1)

        ' 文字列の挿入
        s2 = s2.Insert(6, " inserted")

        Console.WriteLine(s2)

        ' 文字列の削除
        s1 = s1.Remove(0, 7)

        Console.WriteLine(s1)

        ' 文字列の置き換え
        s2 = s2.Replace("sample", "test")

        Console.WriteLine(s2)

    End Sub

End Module
出力結果
s1: sample string, s2: sample string, s1 is s2: True
s1: sample string, s2: sample string, s1 is s2: False
sample string combined
sample inserted string
string combined
test inserted string
Press any key to continue

 この例からわかるとおり、VBにおいて関数として存在していた挿入や置き換えがメソッドとして存在するようになったため、より簡単にコーディングできるようになりました。 もちろん、Microsoft.VisualBasic.Strings名前空間にある関数を使えば、VB同様に関数を用いて文字列操作を行うこともできます。 余談ですが、VB.NETでは文字列に対する複合代入演算子の使用がサポートされるようになったので、これを用いることで文字列連結が比較的簡単にできるようになりました。
 ここまでは文字列型の簡単な操作方法を紹介しましたが、.NET FrameworkにはString型に似たクラスとしてStringBuilderクラスというものがあります。 次のコードは先ほどのコードをStringBuilderを用いたものにし、同様の実行結果を出力させるものです。
StringBuilder型とそのメソッド
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
Imports System.Text

Module Module1

    Sub Main()

        ' StringBuilder は System.Text 名前空間のクラス
        Dim s1 As New StringBuilder("sample string")
        Dim s2 As New StringBuilder("sample string")

        ' 文字列の連結
        s1.Append(" combined")

        Console.WriteLine(s1)

        ' 文字列の挿入
        s2.Insert(6, " inserted")

        Console.WriteLine(s2)

        ' 文字列の削除
        s1.Remove(0, 7)

        Console.WriteLine(s1)

        ' 文字列の置き換え
        s2.Replace("sample", "test")

        Console.WriteLine(s2)

    End Sub

End Module
出力結果
sample string combined
sample inserted string
string combined
test inserted string
Press any key to continue

 まず、StringBuilderクラスのコンストラクタについて説明する必要があります。 このコンストラクタでは、文字列からStringBuilder型のインスタンスを作成しています。 このインスタンスは、内部的にこの文字列を保持し、各メソッドによってこの文字列を操作することができます。 このコードの実行結果を見てわかるとおり、メソッドの形式こそ違いますがString型の時と同じ出力結果になっています。 ここで注目していただきたいのは、String型のメソッドを用いた時にはその結果が必ず戻り値として返されていたのに対し、StringBuilder型を用いたときにはメソッドの結果がそのインスタンスに対してのみ及ぼされ戻り値を必要としていないことです。
 これについて詳しく説明することにします。 実は、Stringクラスはその値を変更することができないという性質があります。 このことを「値が不変」であるといいます。 String型に関する文字列操作では値が変わったように見えますが、実は操作の際に新しいオブジェクトが作成されているのです。 そのため常に戻り値を取得する必要があったのです。 それに対してStringBuilderクラスはメソッドによってオブジェクトの値を変えることができます。 このことを「値が可変」であるといいます。 メソッドによる文字列操作の結果で戻り値を取る必要がなかったのはそのためです。

2.StringBuilderクラスの使用方法


 StringBuilderクラスは内部的に文字列を保持した一種のバッファと考えることができます。 コンストラクタに何も指定しないでインスタンスを作成した場合、最大で16文字格納することができる空のStringBuilderが作成されます。 Append()メソッドによって文字列を追加していく際にこの最大文字数を超えると、インスタンスの容量は自動的に増やされます。 容量が増やされるような操作をしない限りは、メモリの再割り当てや解放等がされることはないので、高速に文字列を操作することができます。 さらに、ToString()メソッドを用いることで、StringBuilderの保持する文字列をString型に変換して取得することもできます。 次のコードでStringBuilderクラスに次々と文字列を追加していった際の容量の変化を見ることができます。
StringBuilderクラスの使用方法
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
Imports System.Text

Module Module1

    Sub Main()

        ' 新しいインスタンスを作成
        Dim s As New StringBuilder()

        ' sの値を表示
        Console.WriteLine(s)

        ' 現在の文字列の長さと、インスタンスの容量 (文字数)
        Console.WriteLine("Length: {0}, Capacity: {1}", s.Length, s.Capacity)

        ' 10文字追加
        s.Append("1234567890")

        ' 表示
        Console.WriteLine(s)

        ' 現在の文字列の長さと、インスタンスの容量 (文字数)
        Console.WriteLine("Length: {0}, Capacity: {1}", s.Length, s.Capacity)

        ' さらに10文字追加
        s.Append("1234567890")

        ' 表示
        Console.WriteLine(s)

        ' 現在の文字列の長さと、インスタンスの容量 (文字数)
        Console.WriteLine("Length: {0}, Capacity: {1}", s.Length, s.Capacity)

    End Sub

End Module
出力結果
Length: 0, Capacity: 16
1234567890
Length: 10, Capacity: 16
12345678901234567890
Length: 20, Capacity: 32
Press any key to continue

 まず、コンストラクタに何も指定しなかった場合は、既定の容量である16文字を格納することができる空のインスタンスが作成されます。 続いてこれに10文字追加すると最大容量は16文字でまだ余裕があるので容量を表すCapacityプロパティは変化しません。 文字列の長さを表すLengthプロパティは、空の状態に10文字を追加したので当然10になります。 この状態からさらに10文字追加すると、現在の容量を超えるので全体の容量が拡張されます。 この実行結果からは32文字分に増加していることがわかります。 長さは当然20です。
 このように、StringBuilderクラスでも文字列を操作することができるということがわかりました。 それではなぜStringクラスとStringBuilderクラスという二つの似たようなクラスが存在するのか、それをこの次で考えてみることにします。

3.StringBuilderクラスを使用する利点


 どのような場合にStringBuilderクラスを使用すればよいのかは、次のコードで示されています。 まずは、コードを見てください。
StringBuilderクラスを使用する利点
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
Imports System.Text

Module Module1

    Sub Main()

        Dim i As Integer
        Dim dtmBegin As DateTime
        Dim tspSpent As TimeSpan

        ' String型を用いた場合
        Dim str As String

        dtmBegin = DateTime.Now

        ' 10240文字の「あ」を連結
        For i = 1 To 10240

            str += "あ"

        Next

        tspSpent = DateTime.Now.Subtract(dtmBegin)

        Console.WriteLine("Spent {0}[ms]", tspSpent.TotalMilliseconds)

        Console.WriteLine("Length: {0}", str.Length)


        ' StringBuilder型を用いた場合

        ' 10240文字分の容量を確保
        Dim sbd As New StringBuilder(10240)

        dtmBegin = DateTime.Now

        ' 10240文字の「あ」を連結
        For i = 1 To 10240

            sbd.Append("あ")

        Next

        tspSpent = DateTime.Now.Subtract(dtmBegin)

        Console.WriteLine("Spent {0}[ms]", tspSpent.TotalMilliseconds)

        Console.WriteLine("Length: {0}, Capacity: {1}", sbd.Length, sbd.Capacity)

    End Sub

End Module
出力結果
Spent 330.4752[ms]
Length: 10240
Spent 0[ms]
Length: 10240, Capacity: 10240
Press any key to continue

 このコードでは、String型とStringBuilder型の二つを使い、「あ」という文字を一文字ずつ10240回連結したときの経過時間を測定しています。 結果を見て明らかなように、StringBuilder型を用いた場合では計測できないほどの一瞬で操作が完了しているのに対し、String型では約300ミリ秒も要しています。 これは、String型は連結のたびに新しいString型のインスタンスが作成されるのに対し、StringBuilder型では既に確保されたバッファに値を書き込んでいるからで、新しく領域を確保・解放する必要がないからです。
 このように、StringBuilder型は高速に文字列を操作することが求められる場合にその効果を発揮するといえます。 StringBuilder型にはまだ紹介していないメソッド、プロパティもいくつかありますが、ここでは省略します。

4.StringReaderクラスとStringWriterクラス


 StringReaderクラスとStringWriterクラスは、文字列自体をストリームとして扱うことができるクラスです。 その使用方法は、StreamReader、StreamWriterとほとんど同じです。 というのも、これらはTextReader、TextWriterの派生クラスであるからです。 まず、StringReaderの簡単な使用方法を次のコードで示します。
StringReaderクラス
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
Imports System.IO

Module Module1

    Sub Main()

        ' 元の文字列
        Dim str As String

        str = "This is a source string." + vbNewLine
        str += "これは元の文字列。" + vbNewLine
        str += "0123456789" + vbNewLine
        str += "ABCDEFGHIJKLMN"

        ' 文字列を表示
        Console.WriteLine(str)


        ' StringReader は System.IO 名前空間のクラス
        Dim reader As New StringReader(str)

        ' 文字列の最後に到達するまでループ
        While (reader.Peek() <> -1)

            ' 一行分読み込み、改行しないで表示
            Console.Write(reader.ReadLine())

        End While

        reader.Close()

        ' 単なる改行
        Console.WriteLine()

    End Sub

End Module
出力結果
This is a source string.
これは元の文字列。
0123456789
ABCDEFGHIJKLMN
This is a source string.これは元の文字列。0123456789ABCDEFGHIJKLMN
Press any key to continue

 このコードではまず、改行文字を含む文字列を作成しています。 これを出力すると当然改行されて出力されます。 20行目ではこのString型の文字列をストリームとしてStringReaderクラスのインスタンスを作成しています。 これ以降のコードでは、文字列の最後に到達するまで一行ずつ読み込み、それを改行しないで表示すると言うことをしています。
 出力結果を見てわかるとおり、文字列の各行が連結されて表示されています。 つまり、文字列を一行ずつ読み込んでいることがわかります。 このようにStringReaderクラスは、既に存在する文字列から文字を読み込み、操作をする際に使用することができます。 テキストボックスに記入されている文字列を読み込み、それに対して操作する場合などに利用できると思います。
 対して書き込みを行うにはStringWriterを使用します。 StringWriterが使用するストリームとしてコンストラクタで渡される文字列型は、次のコードのようにStringBuilder型である必要があります。 なぜなら、String型の値は「不変」であるため、その値を変更することができないからです。 次のコードはStringWriterを使用した例です。
StringWriterクラス
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
Imports System.IO
Imports System.Text

Module Module1

    Sub Main()

        ' 書き込み用のStringBuilderを作成
        Dim builder As New StringBuilder(1024)

        ' 書き込むためのStringWriterを作成
        Dim writer As New StringWriter(builder)

        ' iとiの階乗の値を書き込み
        Dim i As Integer

        For i = 1 To 10

            writer.WriteLine("i = {0}, i! = {1}", i, Factorial(i))

        Next

        writer.Close()

        ' StringBuilderの値を表示
        Console.WriteLine(builder)

    End Sub

    ' nの階乗を求める
    Function Factorial(ByVal n As Integer)

        If n = 0 Then Return 1

        Return n * Factorial(n - 1)

    End Function

End Module
出力結果
i = 1, i! = 1
i = 2, i! = 2
i = 3, i! = 6
i = 4, i! = 24
i = 5, i! = 120
i = 6, i! = 720
i = 7, i! = 5040
i = 8, i! = 40320
i = 9, i! = 362880
i = 10, i! = 3628800

Press any key to continue

 このコードでは、nの階乗を求め、StringWriterを用いてその値をStringBuilderに書き込み、最後に表示するという一見回りくどいことをしています。 ただこの例のように、StringWriterはバッファとして存在するStringBuilder型の文字列に対して書き込みを行えるという点で、これ以外にも活用方法はあると思います。