文字列が文字列を含んでいるかどうかを判定する方法は、
大きく分けて以下の二つです。
◇ Instr関数を利用
If Instr(文字列全体, 検索値) > 0 Then
◇ Like演算子を利用
If 文字列全体 Like "*検索値*" Then
どちらもかなり使われる書き方ですが、
どちらが早いか検証してみましょう。
なお、お忙しい方のために先に結論だけ書きますと、
「どちらも1,000万回やって1秒かからないため、
普通に使う上では差を実感することはない。」
が結論です。
一応、
- 1文字を探すならInstrが早い
- 2文字以上を探すならLikeが早い。
こんな特徴がありましたが、
文章解析のアルゴリズムとか作らないと差を実感できないと思います。
検証の詳細が見たい方は読み進めて下さい。
検証用のコード
Timer関数を使って検証します。
1億回実行しますが、Timer関数の下限を考慮し、
1千万回を10回実行した平均時間を計測します。
Sub Instr関数vsLike演算子(文字列全体 As String, 検索値 As String) Dim Arr処理時間記録(1 To 10) As Double Dim 開始時刻 As Double, 終了時刻 As Double Dim Like条件式 As String: Like条件式 = "*" & 検索値 & "*" Dim 検証回数 As Long For 検証回数 = 1 To 10 開始時刻 = Timer Dim i As Long For i = 1 To 10000000 ' ◇ どちらかをコメントアウトして実行 ' Instr関数 If InStr(文字列全体, 検索値) > 0 Then End If ' Like演算子 If 文字列全体 Like Like条件式 Then End If Next 終了時刻 = Timer Arr処理時間記録(検証回数) = 終了時刻 - 開始時刻 Next Debug.Print WorksheetFunction.Average(Arr処理時間記録) End Sub
※ Windows10、Intel Core i5-10400、メモリ8G、Excel365
検証結果
まずはストレートに「文字列が検索値を含むか」を判定します。
aaaabaaaaを元の文字列として、
以下の文字列を1,000万回検索してみました。(単位は秒)
検索値 | b | aba | aabaa | aaabaaa |
---|---|---|---|---|
Instr関数 | 0.36 | 0.90 | 0.78 | 0.56 |
Like演算子 | 0.52 | 0.50 | 0.48 | 0.49 |
一文字を検索するときはInstr関数が早いですが、
検索値が増えるとLike演算子の方が早くなっていますね。
aaaaaaaaabaaaaaaaaaと元の文字列を長くしてみると、
検索値 | b | aba | aabaa | aaabaaa |
---|---|---|---|---|
Instr関数 | 0.38 | 1.84 | 1.87 | 1.61 |
Like演算子 | 0.54 | 0.74 | 0.70 | 0.76 |
こんな感じです。
Likeはかなり安定していますね。
Instr関数は文字列全体、検索値のどちらが長くなっても、
その影響を受けやすいことが言えそうです。
続いてaaabaaaから「見つからないもの」を検索してみます。
検索値 | c | aca | aacaa | aaacaaa |
---|---|---|---|---|
Instr関数 | 0.36 | 1.29 | 1.16 | 0.78 |
Like演算子 | 0.48 | 0.76 | 0.67 | 0.54 |
Instr関数の「見つかったら終了」っぽい挙動から予想できる通り、
見つからない検索値を探させるとInstrはさらに遅くなります。
最後に「前方一致」をやりましょう。
Instr関数は結果を「=1」にすることで、
Like演算子は前方にある*を消すことで、
それぞれ前方一致を判定できます。
abaaaaaaaから前方を探してみた結果がこちら。
検索値 | a | aba | abaaa | abaaaaa |
---|---|---|---|---|
Instr関数 | 0.36 | 0.37 | 0.38 | 0.38 |
Like演算子 | 0.16 | 0.19 | 0.23 | 0.26 |
Instr関数がだいぶ安定しましたね。
前方の*が消えることでワイルドカードが1つ減ったLikeもかなり高速化しました。
ちなみに、前方一致ならと「Left関数」での判定も試しましたが、
aを探した時点で0.8と遅すぎたのでリタイアしました。
InstrがLong、Like演算子がBooleanを生成しているのに対し、
Left関数はStringをいちいち作る必要があるため当然と言えば当然ですね。
基本的な検証はこんなところです。
- 1文字を探すならInstrが早い
- 2文字以上を探すならLikeが早い。
という感じでしたね。
Instrは発見位置をLongで返す必要がある分、
分割統治法でやりたい放題できるLikeの方が早いって感じでしょうか。
と、考察できそうなところですが、この処理は1,000万回やって1秒かかっていません。
よって大事なのは「どっちも十分早いのでどっちでもよい」と評価することです。
書きやすさと読みやすさで判断して、どちらを使うか決めるのがいいですね。
プログラムには、
「すべての処理を少しずつ早くするよりも、
最も遅い処理をガッツリ早くした方が全体の処理速度は早くなる」
という性質があります。
よって、「どこを早くするべきか」を考えるとの同じくらい、
「どこを早くしても意味がないか」を考えるのも大事です。
この記事からは、
「文字列検索の速度は気にしなくていい」
という情報をお持ち帰りください。
DoとForとか、RangeとCellsとか、LongとIntegerとか、
うんちくレベルまで速度に取り憑かれてしまわないよう気を付けましょう。
おまけ:文字列結合は意外と遅い
Like演算子の、というより&演算子の特徴に触れておきます。
今回のコードは、わざわざ
Dim Like条件式 As String: Like条件式 = "*" & 検索値 & "*"
これを用意してLike演算子に渡していました。
これを、
If 文字列全体 Like "*" & 検索値 & "*" Then
と、文字結合をIfステートメント内に書いた場合は+1秒くらい遅くなります。
「1秒かからない処理を1秒遅くしてしまう」ということは、
文字列の結合は文字列の検索より遅いんですよ。
意外ですよね。
原因は「Stringが可変長なため結合の度に新しいメモリを要するから」な気がします。
まあこれも1,000万回で1秒なのでそこまで気にする必要はないですけどね。
結合は処理によってはかなりの数やることがあるので、
1億回レベルの結合が懸念されるようなら、代替案を検討してください。
なお、重ねてになりますが、
If 文字列全体 Like "*" & 検索値 & "*" Then
こう書いてはいけないという話ではありません。
「速度は気にするべきところだけ気にしろ」という話ですので、
1億回やらないなら、変数作るなんて面倒ですしこれで書きましょう。
ただ、速度以前に「"*" &」みたいな式って、書くのが面倒なんですよね。
しかも読みづらいですし。
なので、
- 「*○○*」の様にLike内にベタ打ちできる場合はLike演算子
- 検索値が変数に入っている場合はInstr関数
と使い分けると、書きやすさ/読みやすさを両立できると思います。
Instr関数とLike演算子は、こんな基準で使い分けてみてください。