文字列の加工

文字列の加工を行うときにはStringBufferを用いて文字列の加工を行う場合が多い。
しかし大変ソースコードが読みづらくなってしまうことがある。


public String getLogFileName(String fileName) {
StringBuffer sb = new StringBuffer(fileName);
sb.append("_");
sb.append(DateFormat.format(new Date()));
sb.append(".log");
return sb.toString();
}
今まで私はJDK1.3から導入されているMessageFormatを用いて文字列の整形をしていた。

public String getLogFileName(String fileName) {
return MessageFormat.format("{0}_{1}.log",new Object[]{fileName,DateFormat.format(new Date())});
}
しかしMessageFormatの元々の目的はロケールなどに依存しないメッセージを作成するためのクラスである。
ただ国内限定のビジネスシステムにおいてはロケールなどを考慮しなくてもよい場合が多い。
そういった場合のログ出力などのように頻繁にMessageFormat#format(String message,Object[] arguments)を用いると実行時のパフォーマンスに影響をあたえる可能性が多い。

public static void main(String[] args) {
new Test().stringBufferImpl();
new Test().messageFormatImpl();
}

public void stringBufferImpl() {
StringBuffer sb = new StringBuffer();
long start = System.currentTimeMillis();
String result = null;
for (int i = 0; i < 1000000; i++) {
sb.append("Hello ");
sb.append(Integer.valueOf(i).toString());
sb.append(" World");
result = sb.toString();
sb.delete(0, sb.length());
}
long end = System.currentTimeMillis();
System.out.println(result);
System.out.println("StringBuffer " + (end - start));
}

public void messageFormatImpl() {
long start = System.currentTimeMillis();
String result = null;
DecimalFormat format = new DecimalFormat("######");
for (int i = 0; i < 1000000; i++) {
result = MessageFormat.format("Hello {0} World",
new Object[] { format.format(i) });
}
long end = System.currentTimeMillis();
System.out.println(result);
System.out.println("MessageFormat " + (end - start));
}

上記を実行した結果は以下のようになった。

Hello 999999 World
StringBuffer 1201
Hello 999999 World
MessageFormat 10275
MessageFormat#formatを用いた場合には9倍近く実行速度に影響を与えているようだ。
StringBufferを使うと高速に処理が完了するが、複雑な文字列の加工を行うとソースコードの可読性が低下する。しかしMessageFormatを用いると場合によっては実行速度に影響がある。
そこでJDK1.4から導入された正規表現を用いたStringFormatterクラスを作成してみた。

import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StringFormatter {

private final Pattern pattern = Pattern.compile("\\{(\\d+)\\}");

private final Matcher matcher;

public StringFormatter(String message) {
this.matcher = pattern.matcher(message);
}

public String format(Object[] values) {
List list = new LinkedList();
for (int i = 0, size = values.length; i < size; i++) {
list.add(values[i].toString());
}
return this.format)((String[]) list.toArray(new String[list.size()]))(;
}

public String format(String[] values) {
matcher.reset();
StringBuffer sb = new StringBuffer();
int groupCount = 0;
while (matcher.find()) {
if (groupCount == values.length) {
throw new IllegalArgumentException();
}
matcher.appendReplacement(sb, values[Integer.parseInt(matcher
.group(1))]);
groupCount++;
}
matcher.appendTail(sb);
return sb.toString();
}

}


public void stringFormatterImpl() {
long start = System.currentTimeMillis();
String result = null;
DecimalFormat format = new DecimalFormat("######");
StringFormatter formatter = new StringFormatter("Hello {0} World");
for (int i = 0; i < 1000000; i++) {
result = formatter.format(new Object[] { format.format(i) });
}
long end = System.currentTimeMillis();
System.out.println(result);
System.out.println("StringFormatter " + (end - start));
}

以下のクラスを用いてみるとMessageFormat#formatと比較して私の環境では20%近く処理速度が向上した。
まとめ
・文字列加工はStringBufferで行うと一番高速であるが複雑な文字列加工を行うとソースコードが読みづらくなる。
・MessageFormatを用いた大量の文字列の加工は処理速度に難があるので直接使用するのは避けた方がよい局面があると思われる。