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

java-surrogateテクノロジー

前回、JavaASCII制御文字の取り扱いについて記事にしました。
そして今回は、前回の対策を応用して、もっと厄介なサロゲートペア文字Javaでゴニョゴニョする方法を2つ程ご紹介したいと思います。

サロゲートペア文字とは?

簡単に説明すると、全世界で文字コードを統一するため、Unicodeという文字コードが一般的に主流になっています。
通常1文字を2バイトで表現するのですが、2バイト(65536文字)では文字が足りなくなってしまったため、1文字を4バイトで表現する方法として「サロゲートペア」が誕生しました。

サロゲートペアは何が問題なの?

今ではそんなに意識することはなくなりましたが、古いシステムや古いデータを取り扱う際に問題が発生してしまいます。
と言うのも、昔はUnicode以外の文字コードも多く使われていました。
それ故にバグ、いわゆる文字化けが発生してしまうのです。
これによって、データの不整合が起きたり、プログラムが想定したとおりに動かなかったりします。

サロゲートペア文字一覧

実際、サロゲートペア文字にはどのような文字があるのか、Qiitaの記事をご覧ください。

サロゲートペア文字を置換・削除する方法

前回のASCII制御文字の方法を応用して、2つの方法でサロゲートペア文字を変換していきます。
以下の2つの例では、「𩸽(ほっけ)」サロゲートペア文字を「(くち)」という漢字に置換します。

1. 正規表現を利用する

前回ご紹介したJava正規表現一覧が掲載されている海外のサイトを見ると「Unicode Categories」にサロゲートペア文字を判定する正規表現がありました。

・・・
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文字ずつループしてサロゲートペア文字を判定します。
判定方法はJavaCharacterクラスに用意されている以下の関数を使います。

  • isSurrogate()
  • isHighSurrogate()
  • isLowSurrogate()

詳しくはこの辺りを参考にしてね!


前回同様、関数を作ってみました!

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つじゃないですね。
前回も書きましたが、正規表現は遅いらしいですが、コンマ何秒の世界なので自分は好きな方で良いと思います。

東京生まれ福岡育ちの文系プログラマー。
テクノロジーの恩恵を感じながら日々精進しています。
広く浅くをモットーに、最近ではプログラミングだけでなく、仮想サーバーからセキュリティ、監視システムなども勉強中です。

管理者をフォローする

コメント

タイトルとURLをコピーしました