CSVでは「,」や「”」「\r」「\n」などの文字列が含まれる場合、それらのデータを「”」で囲む必要があります。
そのためCSVファイルを読み込んで処理を行う場合、”“で囲まれたデータでは”“を削除する処理が必要です。
JavaScriptではこのような処理を実現する方法がひとつではありません。
文字列を切り出すためのメソッドには次のようなものがあります。
また、ここに上げた以外にも正規表現を使って文字列を切り出すこともできるでしょう。
  • String.prototype.substr();
  • String.prototype.substring();
  • String.prototype.slice();
JavaScriptで文字列を切り出すには、これらのうちどの方法を使うのがよいのでしょうか。

検証

副作用の有無

JavaScriptでは配列を操作するArray.prototype.sort()を使うと元の配列の内容が変わってしまうため、Array.prototype.slice()を使うケースがあります。
名前を見る限りString.prototype.slice()に副作用は無いイメージがありますが、他のメソッドについてはどうでしょうか。

調べるまでも無い話ですが、これら3つはすべて非破壊的なメソッドです。
そのため、副作用の有無でこれらのメソッドを使い分ける必要はありません。

コードの読みやすさ

コードの読みやすさに違いはあるでしょうか。
String.prototype.substr()では開始位置と取り出す文字の数を指定するのに対し、他の2つのメソッドは開始位置と終了位置を指定します。

しかし、これらの違いによりコードの可読性が著しく変わるとは思えません。
そのため、コードの読みやすさにより使い分ける必要も無さそうです。

実行速度

副作用の有無や、コードの読みやすさが変わらないのであれば、実行速度の早いものを選べば良さそうです。
正規表現を使った場合に遅くなるのは確実ですが、その他の方法に違いはあるのでしょうか。

次のようなプログラムを書いて、Node.js 7.8.0の環境での実行時間を調べてみました。

const sample = `"${'x'.repeat(30)}"`,
    regexp = /^"?(.+?)"?$/;

// Regular expression
console.time('TIME_01');
for ( let i = 0; i < 1000000; ++i ) {

    const match = sample.match(regexp),
        result = match[1];

}
console.timeEnd('TIME_01');

// String.prototype.substr()
console.time('TIME_02');
for ( let i = 0; i < 1000000; ++i ) {

    const first = sample.substr(0, 1),
        last = sample.substr(-1),
        result = ('"' === first && '"' === last) ? sample.substr(1, sample.length - 2) : sample;

}
console.timeEnd('TIME_02');

// String.prototype.substring()
console.time('TIME_03');
for ( let i = 0; i < 1000000; ++i ) {

    const first = sample.substring(0, 1),
        last = sample.substring(sample.length - 1),
        result = ('"' === first && '"' === last) ? sample.substring(1, sample.length - 1) : sample;

}
console.timeEnd('TIME_03');

// String prototype.slice()
console.time('TIME_04');
for ( let i = 0; i < 1000000; ++i ) {

    const first = sample.slice(0, 1),
        last = sample.slice(sample.length - 1),
        result = ('"' === first && '"' === last) ? sample.slice(1, sample.length - 1) : sample;

}
console.timeEnd('TIME_04');

結果

コードを10回実行して平均を求めた結果は次のようになりました。
正規表現が遅いのは最初から予想がつきましたが、他の方法でも意外と差があるようです。

なお、これはあくまで、文字列の長さが32文字の場合の結果です。
文字列が長くなると正規表現はどんどん遅くなっていくので注意が必要です。

Method Time(ms)
Regular expression 110.5822
String.prototype.substr() 34.1109
String.prototype.substring() 31.9043
String.prototype.slice() 50.8628

結論

String.prototype.substring()では実現できないけれど、String.prototype.substr()やString.prototype.slice()なら実現できるというケースは思いつかないので、基本的にはString.prototype.substring()を使うと覚えて置けば良さそうです。

また実行速度が遅い正規表現ですが、正規表現を使わないと実現できなかったり、込み入った処理を書かなくてはいけないケースもあると思います。
そういった場面では正規表現を使うことになるのですが、正規表現を使わなくても十分にシンプルなコードが書けるのであれば、使わない方がいいでしょう。

おまけ

String.prototype.indexOf()を使って特定の文字を探し出し、その前後を切り出すケースについても実行速度を調べてみました。

結果としてはString.prototype.indexOf()の時刻時間が追加になっただけで、傾向は変わりませんでした。
また、正規表現は複数のメソッドを呼ぶよりもコストが高いことがわかります。

Method Time(ms)
Regular expression 138.8332
String.prototype.substr() 60.6505
String.prototype.substring() 54.8006
String.prototype.slice() 71.2684

String.prototype.indexOf()を使ったコードも載せておきます。

const sample = `${'x'.repeat(16)}y${'z'.repeat(16)}`,
    regexp = /^(.+)y(.+)$/;


// Regular expression
console.time('TIME_01');
for ( let i = 0; i < 1000000; ++i ) {

    const match = sample.match(regexp),
        before = match[1],
        after = match[2];

}
console.timeEnd('TIME_01');

// String.prototype.substr()
console.time('TIME_02');
for ( let i = 0; i < 1000000; ++i ) {

    const index = sample.indexOf('y'),
        before = sample.substr(0, index),
        after = sample.substr(index * -1);

}
console.timeEnd('TIME_02');

// String.prototype.substring()
console.time('TIME_03');
for ( let i = 0; i < 1000000; ++i ) {

    const index = sample.indexOf('y'),
        before = sample.substring(0, index),
        after = sample.substring(index + 1, sample.length);

}
console.timeEnd('TIME_03');

// String prototype.slice()
console.time('TIME_04');
for ( let i = 0; i < 1000000; ++i ) {

    const index = sample.indexOf('y'),
        before = sample.slice(0, index),
        after = sample.slice(index + 1, sample.length);

}
console.timeEnd('TIME_04');

コメントの投稿