綺麗に死ぬITエンジニア

JavaScriptで特定の文字列が含まれているかを調べる7つの方法とそのベンチマーク

2017-02-16

JavaScriptにおいて、文字列の中に特定の文字列が含まれているかどうかを調べる方法はいくつかあります。

時々、どの方法が最適か知りたくなるときがあったので、今回はそれらの方法のおさらいと、それぞれの方法におけるベンチマークを取ってみます。

なお、今回は以下の7つの方法を検証します。

  • String.prototype.indexOf()メソッドを利用する方法
  • String.prototype.includes()メソッドを利用する方法
  • Lodashライブラリに含まれるincludes()メソッドを利用する方法
  • String.prototype.search()メソッドを利用する方法
  • String.prototype.match()メソッドを利用する方法
  • RegExp.prototype.test()メソッドを利用する方法
  • RegExp.prototype.exec()メソッドを利用する方法

各方法について詳しくおさらいしていきましょう。

String.prototype.indexOf()メソッドを利用する方法

indexOf()メソッドは、呼び出すStringオブジェクト中で、fromIndexから検索を始め、指定された値が最初に現れたインデックスを返します。値が見つからない場合は-1を返します。

str.indexOf(searchValue[, fromIndex]);

このメソッドを利用してstringの中にsubstringが含まれているかどうかを調べるには、以下のようにします。

string.indexOf(substring) !== -1

String.prototype.includes()メソッドを利用する方法

includes()メソッドは、1つの文字列を別の文字列の中に見出すことができるかどうかを判断し、必要に応じてtruefalseを返します。

str.includes(searchString[, position]);

このメソッドはES6で追加された比較的新しいものです。古いブラウザでは対応していません。

このメソッドを利用してstringの中にsubstringが含まれているかどうかを調べるには、以下のようにします。

string.includes(substring)

Lodashライブラリに含まれるincludes()メソッドを利用する方法

JavaScriptの便利な関数が詰め込まれたライブラリ「Lodash」を利用している場合は、その中のincludes()メソッドも選択肢の一つになるでしょう。

includes()メソッドは、値がcollectionに含まれているかどうかをチェックします。collectionが文字列の場合は、値の部分文字列があるかどうかがチェックされます。

_.includes(collection, value, [fromIndex=0]);

このメソッドを利用してstringの中にsubstringが含まれているかどうかを調べるには、以下のようにします。

_.includes(string, substring)

String.prototype.search()メソッドを利用する方法

search()メソッドは、対象のStringオブジェクトが正規表現でマッチできるかどうかを調べるためのメソッドです。

str.search(regexp);

このメソッドを利用してstringの中にsubstringが含まれているかどうかを調べるには、以下のようにします。

var regexp = new RegExp(substring);

string.search(regexp) !== -1

String.prototype.match()メソッドを利用する方法

match()メソッドは、対象のStringオブジェクトが正規表現でのマッチングの際に、その結果を得るメソッドです。

str.match(regexp);

このメソッドを利用してstringの中にsubstringが含まれているかどうかを調べるには、以下のようにします。

var regexp = new RegExp(substring);

!!string.match(regexp)

RegExp.prototype.test()メソッドを利用する方法

test()メソッドは、正規表現と対象の文字列の間のマッチを調べるための検索を実行し、結果を示す真偽値を返します。

regexp.test(str);

このメソッドを利用してstringの中にsubstringが含まれているかどうかを調べるには、以下のようにします。

var regexp = new RegExp(substring);

regexp.test(string)

RegExp.prototype.exec()メソッドを利用する方法

exec()メソッドは、特定の文字列における正規表現のマッチングを実行し、結果を得るメソッドです。

regexp.exec(str);

このメソッドを利用してstringの中にsubstringが含まれているかどうかを調べるには、以下のようにします。

var regexp = new RegExp(substring);

!!regexp.exec(string)

ベンチマーク

上記の7つの方法におけるベンチマークを取得します。

今回は、ランダムな100字の英数字からなる文字列が10000個格納された配列を用意し、それをfor文で回して、10000個全ての文字列に対し特定の文字列(今回は's8a')が含まれているか判定する処理を実施します。

実行したソースコードは以下です。興味がない方は読み飛ばしてください。自分の環境で実施してみたい方は、strings配列を用意し、Lodashを読み込めばそのまま実行できると思います。

var strings = [
    '2C9EabkTrgWcjJCYfbgWaE5bRdGrNnrFCy2Ecg8A5AiXE6NVdJBinCNAi8rS23P38ECFU2uwAxT8PEydjDahbURHYrHwVfnA4pWu',
    'J7dmU8r59WwxWBNyufmnJQC4RVgS2k3VbNwLkiai6h8uDgbCXgHEVut2aCStMjxQZ7uTT68X8GdbFTAArC3PAWhJYXGAXwXnfdnh',
    // 以下10000個になるまで続くので省略
];

var substring = 's8a';
var regexp = new RegExp(substring);

var results = [], i;

console.time('indexOf');
for (i = 0; i < strings.length; ++i) {
    results.push(strings[i].indexOf(substring) !== -1);
}
console.timeEnd('indexOf');

results = [];

console.time('includes');
for (i = 0; i < strings.length; ++i) {
    results.push(strings[i].includes(substring));
}
console.timeEnd('includes');

results = [];

console.time('lodash');
for (i = 0; i < strings.length; ++i) {
    results.push(_.includes(strings[i], substring));
}
console.timeEnd('lodash');

results = [];

console.time('search');
for (i = 0; i < strings.length; ++i) {
    results.push(strings[i].search(regexp) !== -1);
}
console.timeEnd('search');

results = [];

console.time('match');
for (i = 0; i < strings.length; ++i) {
    results.push(!!strings[i].match(regexp));
}
console.timeEnd('match');

results = [];

console.time('test');

for (i = 0; i < strings.length; ++i) {
    results.push(regexp.test(strings[i]));
}
console.timeEnd('test');

results = [];

console.time('exec');

for (i = 0; i < strings.length; ++i) {
    results.push(!!regexp.exec(strings[i]));
}
console.timeEnd('exec');

手元のGoogle Chrome(56.0.2924.87)で実行した結果は以下のようになりました。

indexOf: 2.068ms
includes: 2.061ms
lodash: 2.531ms
search: 2.110ms
match: 5.921ms
test: 1.948ms
exec: 1.951ms

正規表現が意外と優秀で、RegExpオブジェクトのメソッド(test()及びexec())を利用するのが最速のようです。

ただ、正規表現の場合、探したい文字列によっては、その文字列を正規表現でも大丈夫なようにエスケープする(参考:JavaScriptで正規表現文字列をエスケープする方法)必要があるので、場合によってはトータルではパフォーマンスが落ちることもあるでしょう。

一般的な用途では、最も有名で速度的にも無難なString.prototype.indexOf()を使うのが良さそうですね。

ただ、全てのメソッドを通して、思ったよりも大差なかったので、可読性やプロジェクトの意向重視で、どのメソッドを使ってもパフォーマンス的には問題ないでしょう。

まとめ

RegExpオブジェクトのメソッドRegExp.prototype.test()及びRegExp.prototype.exec()メソッドを利用する方法が速度面では有利でした。しかし、場面によってはRegExpオブジェクトを用意する際、正規表現文字列へエスケープする必要があります。

実用的には、String.prototype.match()メソッドを利用する方法以外は速度に大差はないので、プロジェクト毎に可読性や方針重視で自由な方法を使っていいと思います。ただ、String.prototype.includes()メソッドを利用する方法は利用できる環境が限られるので、まだ使わない方がいいでしょう。将来的に有利になる可能性はあります。

なお、ほぼ全ての場面において、String.prototype.indexOf()メソッドを利用する方法が最も無難で良い選択肢と言えるでしょう。