2012年11月5日月曜日

String.formatを何度も呼ばれる処理内で使用してはいけない

java.lang.Stringにはformatメソッドが定義されている。
数値の0埋め、3桁区切り、日付の整形など、指定した形式にフォーマットする事に関してはかなり便利なメソッドである。
しかし、Androidプログラミングでこれを使用する場合は要注意。String.formatは内部でFormatterオブジェクトを生成している為かなり遅い。

例えば数値の0埋めなどは、以下のようにStringBuilderを使用して愚直に0を連結すると約6倍高速に動作する。
StringBuilderをnewせずに使いまわすテクニックを利用している事に注意。使いまわさないと速度が約半分になる。それでも十分速いが。

public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// String.formatを使用して100000回試行.
long timeMillis = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String.format("%06d", i);
}

// かかったミリ秒を出力するイディオム. 前の時間 - (次の時間比較の為に代入しつつ現在の時間)、の全体をマイナスに. 式が左から評価される為.
System.out.println(- (timeMillis - (timeMillis = System.currentTimeMillis())));

// 愚直な文字列連結での100000回試行.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {

// StringBuilderの中身を空にする.
sb.setLength(0);

sb.append(i);
for (int j = 6, size = sb.length(); j > size; j--) {
sb.insert(0, "0");
}

// これが無ければ約30倍高速に動作するが, 結果をStringに合わせる為仕方ない.
sb.toString();
}
System.out.println(- (timeMillis - (timeMillis = System.currentTimeMillis())));
}
}

05-08 13:10:13.250: I/System.out(18793): 6575
05-08 13:10:14.331: I/System.out(18793): 1084
日付フォーマットの例。android.text.format.Timeが使用できる状況であれば、下記のように15倍ほど高速に動作する。
使えない状況であったとしても、SimpleDateFormatをループ外でnewし、適宜formatした方が高速に動作する。


public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Date date = new Date();

long timeMillis = System.currentTimeMillis();

// String.formatを使用して10000回試行.
String formatStr = "%1$tY%1$tm%1$td";
for (int i = 0; i < 10000; i++) {
String.format(formatStr, date);
}
System.out.println(- (timeMillis - (timeMillis = System.currentTimeMillis())));

// SimpleDateFormatを使用して10000回試行.
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
for (int i = 0; i < 10000; i++) {
sdf.format(date);
}
System.out.println(- (timeMillis - (timeMillis = System.currentTimeMillis())));

// android.text.format.Timeを使用して10000回試行.
Time time = new Time();
time.set(date.getTime());
formatStr = "%Y%m%d";
for (int i = 0; i < 10000; i++) {
time.format(formatStr);
}
System.out.println(- (timeMillis - (timeMillis = System.currentTimeMillis())));
}
}

05-08 13:40:49.091: I/System.out(19604): 2948
05-08 13:40:50.322: I/System.out(19604): 1230
05-08 13:40:50.502: I/System.out(19604): 186
業務では目に見えてこのようなループ処理が行われている事が少ないが、メソッド内でString.formatを使用しており、それが暗に何百回も呼ばれている、という事がままある。

0 件のコメント:

コメントを投稿