Javaでサロゲートペア文字を置換したり除去する2つの方法

前回、JavaでASCIIの制御文字の取り扱いについて記事にしました。
そして今回は、前回の対策を応用して、もっと厄介なサロゲートペア文字をJavaでゴニョゴニョする方法を2つ程ご紹介したいと思います。
今までJavaに関する記事を投稿したことはありませんが、仕事で少しだけ文字コードの変換を行う際に使いましたので、記事にしたいと思います。何分、Javaに関しては初心者ですので、誤り等ありましたらご指摘の程よろしくお願い致します。初回はASCII制御文字を置換す... JavaでASCIIの制御文字を置換する3つの方法 - Minory |
サロゲートペア文字とは?
簡単に説明すると、全世界で文字コードを統一するため、Unicodeという文字コードが一般的に主流になっています。
通常1文字を2バイトで表現するのですが、2バイト(65536文字)では文字が足りなくなってしまったため、1文字を4バイトで表現する方法として「サロゲートペア」が誕生しました。
Windows VistaのJIS2004対応により、WindowsのUnicode環境で使用できる日本語漢字が907字追加されました。しかしこの中には「サロゲートペア」と呼ばれる文字が含まれており、文字を取り扱うには適切な処理が必要になります。 サロゲートペア入門 - Codezine |
サロゲートペアは何が問題なの?
今ではそんなに意識することはなくなりましたが、古いシステムや古いデータを取り扱う際に問題が発生してしまいます。
と言うのも、昔はUnicode以外の文字コードも多く使われていました。
それ故にバグ、いわゆる文字化けが発生してしまうのです。
これによって、データの不整合が起きたり、プログラムが想定したとおりに動かなかったりします。
サロゲートペア文字一覧
実際、サロゲートペア文字にはどのような文字があるのか、Qiitaの記事をご覧ください。
JavaでTwitterのような140文字制限をかけようとするときに考える必要がある。サロゲートペアについて。## サロゲートペアとはサロゲートペアとは、16 ビット符号単位の組による単一の抽象文字の表現である。 UTF-1... Java サロゲートペア文字数カウント - Qiita - Qiita |
サロゲートペア文字を置換・削除する方法
前回のASCII制御文字の方法を応用して、2つの方法でサロゲートペア文字を変換していきます。
以下の2つの例では、「𩸽(ほっけ)」サロゲートペア文字を「口(くち)」という漢字に置換します。
1. 正規表現を利用する
前回ご紹介したJavaの正規表現一覧が掲載されている海外のサイトを見ると「Unicode Categories」にサロゲートペア文字を判定する正規表現がありました。
Regex Tutorial - Unicode Characters and Properties - regular-expressions.mobi |
・・・
\p{Cs} or \p{Surrogate}: one half of a surrogate pair in UTF-16 encoding.
・・・
\p{Cs}
か\p{Surrogate}
どちらでも同じ動きをするようですね。
これをreplaceAll()
で使うとこんな感じです。
String str = "𩸽";
str.replaceAll("\\p{Cs}", "口");
そして、変換してみた結果がこれ。
口口
2文字になった!?
なるほど…。
前述の通り、通常1文字を2バイトで表現しているので、1文字4バイトであるサロゲートペア文字は2文字扱いになってしまうようです。
そこで、上位サロゲートと下位サロゲートに分けて変換することにします。
同じく上記のサイトを見ると「Unicode Blocks」の92.と94.に上位サロゲート(InHigh_Surrogates
)と下位サロゲート(InLow_Surrogates
)を判定する正規表現がありました。
この辺りです。
・・・
92. \p{InHigh_Surrogates}: U+D800–U+DB7F
93. ・・・
94. \p{InLow_Surrogates}: U+DC00–U+DFFF
・・・
上位サロゲートだけ、または下位サロゲートだけ変換してしまうと、どちらかの2バイト分が残ってしまうので、片方は削除します。
String str = "𩸽";
str.replaceAll("\\p{InHigh_Surrogates}", "口");
str.replaceAll("\\p{InLow_Surrogates}", "");
上記の例では、上位サロゲートを置換し、下位サロゲートは空文字にしました。
当然、結果は1文字で思った通りになりました。
口
ただ削除するだけの場合は、前者の方法で1発で便利ですね。
置換する場合は後者の方が応用が利くかもしれません。
2. 1文字ずつ判定する
続いて、こちらも前回の応用で、1文字ずつループしてサロゲートペア文字を判定します。
判定方法はJavaのCharacterクラスに用意されている以下の関数を使います。
- isSurrogate()
- isHighSurrogate()
- isLowSurrogate()
詳しくはこの辺りを参考にしてね!
Character (Java Platform SE 7) - docs.oracle.com |
public static String replaceSurrogate(String str) {
if (str == null) {
return str;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (Character.isSurrogate(str.charAt(i))) {
sb.append("口");
}
String result = sb.toString();
return result;
}
もちろん、これだけだと正規表現と同じように「口口」2文字になってしまいますね。
ですので、こちらも同じように上位サロゲートのみ置換、下位サロゲートは空文字にしてしまいましょう。
以下、抜粋。
・・・
if (Character.isHighSurrogate(str.charAt(i))) {
sb.append("口");
}
if (Character.isLowSurrogate(str.charAt(i))) {
sb.append("");
}
・・・
または、上位サロゲートを置換した後、ループを進めて下位サロゲートを無視する方法もありますね!
・・・
if (Character.isHighSurrogate(str.charAt(i))) {
sb.append("口");
i++;
}
・・・
以上、こんな感じでいかがでしたでしょうか。
方法は1つじゃないですね。
前回も書きましたが、正規表現は遅いらしいですが、コンマ何秒の世界なので自分は好きな方で良いと思います。
コメント
コメントを投稿