文字列を高速に連結する(Midステートメント)|Excel VBA |
Midステートメントは文字列の一部を入れ替える命令です。
次の構文のように、変数stringvarの一部をstringで指定した文字列に置き換えます。
同じ名前でMid関数という関数がありますが、Mid関数は文字列の中から任意の文字列を取り出して返します。名前が同じで紛らわしいですが、構文が異なります。
構文 Mid(stringvar, start, length) = string
設定項目 | 内容 |
---|---|
stringvar | 変更する文字列の変数名を指定[省略不可] |
start | 置換開始位置を指定[省略不可] |
length | 置換する文字数を指定[省略可] |
string | 置換する文字列を指定[省略不可] |
次のサンプルはMidステートメントを使って文字列を置換する例です。
Sub Sample1()
Dim Msg As String
Msg = "123456789"
Mid(Msg, 3, 1) = "moug"
Debug.Print Msg '→ 12m456789 3文字目から1文字だけ置換する
Msg = "123456789"
Mid(Msg, 3) = "moug"
Debug.Print Msg '→ 12moug789 引数lengthを省略するとすべて置換する
Msg = "123456789"
Mid(Msg, 1) = "www.moug.net"
Debug.Print Msg '→ www.moug. 10文字目以降が切れる
Msg = "123456789"
Mid(Msg, 3) = "モーグ"
Debug.Print Msg '→ 12モーグ6789 全角文字の場合
End Sub
ただし、変数Msgの長さが9文字のとき、引数startに10以上を指定するとエラーになります。
Msg = "123456789"
Mid(Msg, 10) = "www.moug.net" '→ エラー
文字列を連結するときは、次のコードのように&演算子を使用します。
Msg = "モーグ" & "即効" & "テクニック"
&演算子のかわりに+演算子を使うこともできますが、文字列が数字のときなど、思わぬ結果になることもあるので+演算子は使わない方が良いでしょう。
文字列の連結は、上のコードがもっとも一般的な方法ですが、Midステートメントで文字列を連結することもできます。このとき、連結後の文字列の長さの分だけ、あらかじめ変数の領域を確保しておくことがポイントです。
Sub Sample2()
Dim Msg As String
Msg = Space(10)
Mid(Msg, 1) = "モーグ"
Mid(Msg, 4) = "即効"
Mid(Msg, 6) = "テクニック"
MsgBox Msg
End Sub
文字列をカンマで区切って連結したり、改行しながら連結させたいといったケースもよくあります。
Msg = "モーグ" & "," & "即効" & "," & "テクニック"
この場合は、あらかじめ変数Msgに文字数分のカンマ「,」や改行コードをいれておいてからMidステートメントで連結すると、上のコードと同じ結果が得られます。
Sub Sample3()
Dim Msg As String
Msg = String(12, ",")
Mid(Msg, 1) = "モーグ"
Mid(Msg, 5) = "即効"
Mid(Msg, 8) = "テクニック"
MsgBox Msg
End Sub
文字列の連結をするのに「あらかじめ連結後の文字列領域を確保しておく」のが面倒ですが、Midステートメントによる文字列の連結は&演算子による連結よりも高速である、というメリットがあります。
50kb級以上の長い文字列を&演算子で文字列を連結する場合、内部で次のような処理が行われます。
上記の2)と6)で元の文字列をコピーするため、繰り返しコピーするデータサイズが大きくなるほど処理時間は長くなります。
これに対し、Midステートメントの方は連結後の文字列の領域を一度だけ確保し、その領域内のデータを上書きしていきます。
(参考:Microsoftサポート)
では、どのくらい処理速度が異なるのか比較してみましょう。
サンプル4は&演算子を使って文字列を連結します。
Sub ConcatinateWithAmpersand(N As Long)
Dim Msg As String
Dim Data As Variant
Dim s As Variant
Data = Range(Cells(1, 1), Cells(N, 1)).Value '---(1)
For Each s In Data
Msg = Msg & s
Next s
End Sub
サンプル5はMidステートメントを使って文字列を連結します。
Sub ConcatinateWithMid(N As Long)
Dim Msg As String
Dim Data As Variant
Dim L As Long
Dim s As Variant
Dim pt As Long
Data = Range(Cells(1, 1), Cells(N, 1)).Value '---(1)
'連結後の文字列長を求める
For Each s In Data
L = L + Len(s)
Next s
'変数Msgのスペースを確保する
Msg = Space(L)
pt = 1 '文字位置
For Each s In Data
Mid(Msg, pt, Len(s)) = s
pt = pt + Len(s)
Next s
End Sub
アクティブシートのセルA1:A50000には「モーグ」の文字列が入力されています。
どちらのサンプルも引数Nを受け取ります。
(1)で参照するセル範囲を変えることにより、連結回数を変化させて計測しました。
連結回数が多くなるほど差は顕著になっています。
ここで&演算子による連結の、処理時間の増え方に注目してみます。
1,000回 0.972ミリ秒 → 5,000回 5.031ミリ秒
1,000回 0.972ミリ秒 → 10,000回 20.546ミリ秒
連結回数5倍で時間もおよそ5倍、連結回数10倍でおよそ20倍です。
3,000回 1.801ミリ秒 → 30,000回 184.153ミリ秒
5,000回 5.031ミリ秒 → 50,000回 608.984ミリ秒
連結回数10倍で、時間が100倍以上になっています。
&演算子で50kb級以上の大きな文字列を連結すると、一時領域を用意することで文字列のコピーが2回行われます。「モーグ」は6バイトなので、連結後の文字列が50kb以上になるのは、計算上は8334回目で、上の表で赤線になっているあたりから連結の際に2回のコピーが行われていることになります。
他の要因もあるので、正確に、50kb以下は文字列の長さに比例、50kb以上は長さのべき乗に比例というわけにはいきませんが、1つの目安として頭の片隅に入れておくといいかもしれません。
これをグラフにすると次のようになります。
連結回数が少なく文字列サイズが小さいうちは、その差はあまり大きくありませんが、長い文字列を何度も連結するようなケースでは、Midステートメントの方が圧倒的に有利ですね。
ケースに応じて使い分けるとよいでしょう。
どちらのサンプルも(1)のコードでは指定したセル範囲の値(Valueプロパティの値)をバリアント型の配列に代入しています。このコードを
Set Data = Range("A1:A50000")
のようにSetステートメントを使ってオブジェクト変数に代入してしまうと、以降の処理でRangeオブジェクトに何度もアクセスすることになり、非常に遅くなるので注意してください。
長い文字列の連結はMidステートメントが高速ですが、可読性の点では&演算子による結合の方が直観的でわかりやすいですね。
そこで次善の策として、カッコをつける方法をご紹介します。
次のように短い文字列部分の連結を先に行うことで、長い文字列の連結回数を減らすことになり、処理速度を向上できます。
Msg = Msg & "最後まで" & "読んでくれて" & "ありがとう!"
↓
Msg = Msg & ("最後まで" & "読んでくれて" & "ありがとう!")