文字列の補助文字対応をしていてハマッタのでメモ。
症状
下記のようにUnicodeのコードポイント単位で処理をしたいが、
offsetByCodePointsの値に期待値がこないで、妙にでかい値がくる。なぜ?!
for (int i = 0; i < s.length(); i = s.offsetByCodePoints(i, 1)) {
...
}
調査
下記のようなテストプログラムでoffsetByCodePointsの出力を調べる。
期待値は1.
class Test {
public static void main(String[] args) throws Exception {
String org = "*****ハロー";
String word = org.substring(5, org.length());
p("offsetByCodePoints:%s\n", word.offsetByCodePoints(0, 1));
p("%s\n", word);
}
public static void p(String format, Object... args)
{
System.out.printf(format, args);
}
}
しかし、出力は以下となる。
$ java Test
offsetByCodePoints: 6
ハロー
この6という数字はなにか。
JavaDocによると、String#substringは新しいStringのインスタンスを返すと記述されている。
しかし実はsubustringで生成されたインスタンスは、完全に新しく生成されるわけではないのだ。
Stringのインスタンスは内部に文字列を保持しているが、メモリ効率をよくするために、
同じ文字列をnew Stringした場合は、同じ文字列をメモリ上指し示すような作りになっている。
subustringメソッドを実効した場合も同様で、subustringを実効したStringインスタンスと
同じ文字列を指すのだ(内部にオフセット値をもっていて、この値を変えているだけ。Stringクラスのソースを読むと分かる)
このことから以下のようなループ処理をするときは、substringを使用してはいけない。
for (int i = 0; i < s.length(); i = s.offsetByCodePoints(i, 1)) {
...
}
使用する場合は、
String word = new String(org.substring(5, org.length()));
としなくてはならい。
結論
substringで生成された文字列に対して、offsetByCodePointsを使用してはならない。
じゃあ、どうやって文字ループを実現するかだが、以下のようにした方がいいかもしれない。
for (int i = 0; i < s.length(); ) {
int c = s.codePointAt(i);
p("%x\n", c);
i += Character.charCount(c);
}
offsetByCodePointsはループに使用するなってことだね。
今回使用したテストプログラム
import java.io.*;
class Test {
public static void main(String[] args) throws Exception {
String org = "*****ハロー";
String word = org.substring(5, org.length());
p("offsetByCodePoints:%s\n", word.offsetByCodePoints(0, 1));
p("%s\n", word);
testLoop1(word);
testLoop2(word);
}
public static void p(String format, Object... args)
{
System.out.printf(format, args);
}
public static void testLoop1(String s) {
p("===================\n");
p("offsetByCodePoints\n");
for (int i = 0; i < s.length(); i = s.offsetByCodePoints(i, 1)) {
int c = s.codePointAt(i);
p("%x\n", c);
}
}
public static void testLoop2(String s) {
p("===================\n");
p("charCount\n");
for (int i = 0; i < s.length(); ) {
int c = s.codePointAt(i);
p("%x\n", c);
i += Character.charCount(c);
}
}
}
追記
この症状はバグとして報告されていて、Java6で実行したら問題なかったよ。。。
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6242664