文字列連結から見るシステム内で扱う型について
Stringの連結はそう簡単なものではない - じゅんいち☆かとうの技術日誌
いまさらなのか、いまだになのか、文字列結合ネタからの派生です。
実際、業務アプリケーションで文字列結合が致命的なパフォーマンス劣化を引き起こすようなことは無いと思います。ですが、Javaを使うなら知っていて当然になっている事でもありまして。こういうのは普段からどの程度クラスの性質や扱い方を意識しているかのリトマス試験紙的なものになるのかなと思ったりします。理解した上で可読性重視の選択をするのも当然有りで、選択した結果であれば十二分以上の理由になると思います。ある程度フォーマット等で可読性の改善は出来るものの、StringBuilder.append とか並べるのって見難いですし。
それよりも、最近 String を扱う際に「文字列である必要があるの?」と疑問を持つことが多いです。「このデータは、この瞬間、String であることが本当に適切なのか?」と。確かに人の目に触れる瞬間は常に文字列です。それこそ数値であれ、日付であれ、文字列となってから表示されます。ですが、システム内で文字列として扱われている多くのデータが、文字列である必要がある場面って実際使われているより少ないのではないかと感じています。*1
大層なタイトル付けた気がするけど、ムズカシイ話は以上で終わり。
で、せっかくなので遊んでみました。元ネタに書かれているコードを殆ど書き写した感じですけれど、System.nanoTime() も使ってみたかったのです。
System.nanoTime() は昨日知りました。このブログ見て。 → Javaパフォーマンス計測 そんなタイマーで大丈夫か? - プログラマーの脳みそ
package test; public class StringTest { public static void main(String[] args) { final int loop = 100000; // +演算子 EditStopwatch plus = new EditStopwatch() { String editString() { String value = ""; for (int i = 0; i < loop; i++) value += "a"; return value; } }; // String.concat EditStopwatch concat = new EditStopwatch() { String editString() { String value = ""; for (int i = 0; i < loop; i++) value = value.concat("a"); return value; } }; // StringBuffer.append EditStopwatch stringbuffer = new EditStopwatch() { String editString() { StringBuffer value = new StringBuffer(); for (int i = 0; i < loop; i++) value.append("a"); return value.toString(); } }; // StringBuilder.append EditStopwatch stringbuilder = new EditStopwatch() { String editString() { StringBuilder value = new StringBuilder(); for (int i = 0; i < loop; i++) value.append("a"); return value.toString(); } }; // オマケ EditStopwatch array = new EditStopwatch() { String editString() { char[] arr = new char[loop]; for (int i = 0; i < loop; i++) arr[i] = 'a'; return new String(arr); } }; // 実行 plus.execute(); concat.execute(); stringbuffer.execute(); stringbuilder.execute(); array.execute(); // 結果 System.out.println("plus :" + plus.time); System.out.println("concat :" + concat.time); System.out.println("stringbuffer :" + stringbuffer.time); System.out.println("stringbuilder :" + stringbuilder.time); System.out.println("array :" + array.time); } } abstract class EditStopwatch { long time; String value; abstract String editString(); void execute() { long begin = System.nanoTime(); value = editString(); long end = System.nanoTime(); time += (end - begin); } }
結果
・1回目 plus :6012479095 concat :3287798932 stringbuffer :8282611 stringbuilder :5590036 array :2138235 ・2回目 plus :5944232670 concat :2999206984 stringbuffer :6346267 stringbuilder :5552395 array :2133530 ・3回目 plus :5899566279 concat :2962490857 stringbuffer :9840413 stringbuilder :5610139 array :2441069
流石に配列は反則ですかね。くっつける文字も String じゃなく char にしちゃってますし。でも「String で扱う必要なくね?」って感覚はこの辺から来てたりもします。処理前にサイズが判って、それから文字を足していくーならば配列でいいじゃん、とか言い出すともっと、こう、色々方法はあるでしょうけれども。でも途中で文字列である必要は全く無くて、適した形で扱うのがいいと思います。*2
リソースは殆ど無限に近いと感じるでしょうが、実際は有限です。出来るところは節約した方がいいんじゃないの、とか思ったりもします。
(追記1)ループ数変えても面白いですよ。
・10,000回ループ plus :87346818 concat :31026002 stringbuffer :1725901 stringbuilder :807131 array :281448 ・1,000ループ plus :2619005 concat :1092001 stringbuffer :384532 stringbuilder :383676 array :27375