Java とサロゲートペアについて

サロゲートペアを知らない人は、その挙動に戸惑うだろう。直感に反する挙動をするからだ。

次の Java コードを実行すると何が表示されるだろうか。

String val = "𠮷";
System.out.println(val.length());

val は見た目上は一文字なので 1 と表示することを期待するが、実際の結果は 2 と表示される。これは "𠮷" という文字はサロゲートペア文字で、2byte 文字では表せない文字だからである。この文字を表示するために内部では char 2つ(即ち4byte)を用いてこの一文字を表している。この「2つのcharで1つの文字を表す」文字をサロゲートペアと言う。

見た目上の文字数と内部で表される文字数が異なるため、冒頭のコードは2を返したわけだ。
さて、次のコードを実行するとエラーにならず、"𠮷" を構成する2つの文字が取得できることがわかる。

String val = "𠮷";
System.out.println(Integer.toHexString(val.charAt(0)));
System.out.println(Integer.toHexString(val.charAt(1)));

// 実行結果
d840
dc0b

このサロゲートペアを構成する2つの文字のうち上位のものをハイサロゲート、下位のものをローサロゲートと呼ぶ。


サロゲートペアと仲良くなる方法

幸い Java 上でサロゲートペアを扱う際のノウハウについて素晴らしい記事が IBM DeveloperWorks にまとめられているので詳細な解説はこちらに任せるとしよう。
http://www.ibm.com/developerworks/jp/ysl/library/java/j-unicode_surrogate/

要約すると、

  • char の数 != 文字数
  • char で扱えない文字は int 型のコードポイント値として表すべし
  • 文字列からコードポイント配列の変換は下記コードを使うべし
int[] toCodePointArray(String str) {
    int len = str.length(); // the length of str
    int[] acp = new int[str.codePointCount(0, len)];

    for (int i = 0, j = 0; i < len; i = str.offsetByCodePoints(i, 1)) {
        acp[j++] = str.codePointAt(i);
    }
    return acp;
}

サロゲートペアを出力しよう

さて、少し方向性を変えて、Java で全サロゲートペア文字を出力するコードを書いてみる。サロゲートペアとして使えるスペースは約100万個定義されているため、文字数が多く使える文字かどうかはフォントによって変わってくる。このサンプルプログラムはシステム内のフォントで表示可能なサロゲートペア文字を出力するプログラムだ。

結果はシステムによって異なる。

public class Sample {

    static List<Font> fonts = new ArrayList<Font>();

    static {
        // 実行環境内の全フォントを取得
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        String[] fontNames = ge.getAvailableFontFamilyNames();
        for (String name : fontNames)
            fonts.add(Font.decode(name));
    }

    public static void main(String[] args) throws SQLException, IOException {
        // char 2文字分の全範囲を探索
        for (char i = 0; i < 65535; i++) {
            for (char j = 0; j < 65535; j++) {
                // 非サロゲートペアはスキップ
                if (!Character.isSurrogatePair(i, j))
                    continue;

                int codepoint = Character.toCodePoint(i, j);
                // 表示可能なら表示
                if (canDisplay(codepoint))
                    System.out.println(Character.toChars(codepoint));
            }
        }
    }

    // 任意のコードポイントがシステム内のフォントで表示可能かどうか調べる
    private static boolean canDisplay(int codepoint) {
        for (Font font : fonts)
            if (font.canDisplay(codepoint))
                return true;

        return false;
    }
}

全フォント * char 二文字分の範囲 を探索しているのでそれなりに時間がかかる。
もし特定のフォントについて調べたければ canDisplay メソッドを下記のように書き換えれば OK だ。

private static boolean canDisplay(int codepoint) {
    Font font = Font.decode("MeiryoKe_PGothic"); // 任意のフォント名にする
    return font.canDisplay(codepoint);
}

漢字のサロゲートペア文字一覧

最後に漢字のサロゲートペア文字を一覧で書き記しておこう。
hatena 側によって一部の文字が別の非サロゲートペア文字に置き換えられているので正確な表示ではないが、コードポイントは正確だ。

𠀋 0x2000b 𠂉 0x20089 𠂊 0x2008a 𠂢 0x200a2 𠂤 0x200a4 𠂰 0x200b0 𠃵 0x200f5 𠅘 0x20158 𠆢 0x201a2 𠈓 0x20213
𠌫 0x2032b 𠍱 0x20371 𠎁 0x20381 𠏹 0x203f9 𠑊 0x2044a 𠔉 0x20509 𠖱 0x205b1 𠗖 0x205d6 𠘑 0x20611 𠘨 0x20628
𠛬 0x206ec 𠝏 0x2074f 𠠇 0x20807 𠠺 0x2083a 𠢹 0x208b9 𠤎 0x2090e 𠥼 0x2097c 𠦝 0x2099d 𠩤 0x20a64 𠫓 0x20ad3
𠬝 0x20b1d 𠮟 0x20b9f 𠮷 0x20bb7 𠵅 0x20d45 𠵘 0x20d58 𠷡 0x20de1 𠹤 0x20e64 𠹭 0x20e6d 𠺕 0x20e95 𠽟 0x20f5f
𡈁 0x21201 𡈽 0x2123d 𡉕 0x21255 𡉴 0x21274 𡉻 0x2127b 𡋗 0x212d7 𡋤 0x212e4 𡋽 0x212fd 𡌛 0x2131b 𡌶 0x21336
𡍄 0x21344 𡏄 0x213c4 𡑭 0x2146d 𡑮 0x2146e 𡗗 0x215d7 𡙇 0x21647 𡚴 0x216b4 𡜆 0x21706 𡝂 0x21742 𡢽 0x218bd
𡧃 0x219c3 𡨚 0x21a1a 𡱖 0x21c56 𡴭 0x21d2d 𡵅 0x21d45 𡵢 0x21d62 𡵸 0x21d78 𡶒 0x21d92 𡶜 0x21d9c 𡶡 0x21da1
𡶷 0x21db7 𡷠 0x21de0 𡸳 0x21e33 𡸴 0x21e34 𡼞 0x21f1e 𡽶 0x21f76 𡿺 0x21ffa 𢅻 0x2217b 𢈘 0x22218 𢌞 0x2231e
𢎭 0x223ad 𢘉 0x22609 𢛳 0x226f3 𢡛 0x2285b 𢢫 0x228ab 𢦏 0x2298f 𢪸 0x22ab8 𢭆 0x22b46 𢭏 0x22b4f 𢭐 0x22b50
𢮦 0x22ba6 𢰝 0x22c1d 𢰤 0x22c24 𢷡 0x22de1 𢹂 0x22e42 𢿫 0x22feb 𣆶 0x231b6 𣇃 0x231c3 𣇄 0x231c4 𣇵 0x231f5
𣍲 0x23372 𣏐 0x233d0 𣏒 0x233d2 𣏓 0x233d3 𣏕 0x233d5 𣏚 0x233da 𣏟 0x233df 𣏤 0x233e4 𣏾 0x233fe 𣑊 0x2344a
𣑋 0x2344b 𣑑 0x23451 𣑥 0x23465 𣓤 0x234e4 𣕚 0x2355a 𣖔 0x23594 𣗄 0x235c4 𣘸 0x23638 𣘹 0x23639 𣘺 0x2363a
𣙇 0x23647 𣜌 0x2370c 𣜜 0x2371c 𣜿 0x2373f 𣝣 0x23763 𣝤 0x23764 𣟧 0x237e7 𣟿 0x237ff 𣠤 0x23824 𣠽 0x2383d
𣪘 0x23a98 𣱿 0x23c7f 𣲾 0x23cbe 𣳾 0x23cfe 𣴀 0x23d00 𣴎 0x23d0e 𣵀 0x23d40 𣷓 0x23dd3 𣷹 0x23df9 𣷺 0x23dfa
𣽾 0x23f7e 𤁋 0x2404b 𤂖 0x24096 𤄃 0x24103 𤇆 0x241c6 𤇾 0x241fe 𤋮 0x242ee 𤎼 0x243bc 𤏐 0x243d0 𤘩 0x24629
𤚥 0x246a5 𤟱 0x247f1 𤢖 0x24896 𤩍 0x24a4d 𤭖 0x24b56 𤭯 0x24b6f 𤰖 0x24c16 𤴔 0x24d14 𤸄 0x24e04 𤸎 0x24e0e
𤸷 0x24e37 𤹪 0x24e6a 𤺋 0x24e8b 𤿲 0x24ff2 𥁊 0x2504a 𥁕 0x25055 𥄢 0x25122 𥆩 0x251a9 𥇍 0x251cd 𥇥 0x251e5
𥈞 0x2521e 𥉌 0x2524c 𥐮 0x2542e 𥒎 0x2548e 𥓙 0x254d9 𥔎 0x2550e 𥖧 0x255a7 𥙿 0x2567f 𥝱 0x25771 𥞩 0x257a9
𥞴 0x257b4 𥡴 0x25874 𥧄 0x259c4 𥧌 0x259cc 𥧔 0x259d4 𥫣 0x25ae3 𥫤 0x25ae4 𥫱 0x25af1 𥮲 0x25bb2 𥱋 0x25c4b
𥱤 0x25c64 𥶡 0x25da1 𥸮 0x25e2e 𥹖 0x25e56 𥹢 0x25e62 𥹥 0x25e65 𥻂 0x25ec2 𥻘 0x25ed8 𥻨 0x25ee8 𥼣 0x25f23
𥽜 0x25f5c 𥿔 0x25fd4 𥿠 0x25fe0 𥿻 0x25ffb 𦀌 0x2600c 𦀗 0x26017 𦁠 0x26060 𦃭 0x260ed 𦉰 0x26270 𦊆 0x26286
𦍌 0x2634c 𦐂 0x26402 𦙾 0x2667e 𦚰 0x266b0 𦜝 0x2671d 𦣝 0x268dd 𦣪 0x268ea 𦥑 0x26951 𦥯 0x2696f 𦦙 0x26999
𦧝 0x269dd 𦨞 0x26a1e 𦩘 0x26a58 𦪌 0x26a8c 𦪷 0x26ab7 𦫿 0x26aff 𦰩 0x26c29 𦱳 0x26c73 𦲞 0x26c9e 𦳝 0x26cdd
𦹀 0x26e40 𦹥 0x26e65 𦾔 0x26f94 𦿶 0x26ff6 𦿷 0x26ff7 𦿸 0x26ff8 𧃴 0x270f4 𧄍 0x2710d 𧄹 0x27139 𧏚 0x273da
𧏛 0x273db 𧏾 0x273fe 𧐐 0x27410 𧑉 0x27449 𧘔 0x27614 𧘕 0x27615 𧘱 0x27631 𧚄 0x27684 𧚓 0x27693 𧜎 0x2770e
𧜣 0x27723 𧝒 0x27752 𧦅 0x27985 𧦴 0x279b4 𧪄 0x27a84 𧮳 0x27bb3 𧮾 0x27bbe 𧯇 0x27bc7 𧰼 0x27c3c 𧲸 0x27cb8
𧵳 0x27d73 𧶠 0x27da0 𧸐 0x27e10 𧾷 0x27fb7 𨂊 0x2808a 𨂻 0x280bb 𨉷 0x28277 𨊂 0x28282 𨋳 0x282f3 𨏍 0x283cd
𨐌 0x2840c 𨑕 0x28455 𨕫 0x2856b 𨗈 0x285c8 𨗉 0x285c9 𨛗 0x286d7 𨛺 0x286fa 𨥆 0x28946 𨥉 0x28949 𨥫 0x2896b
𨦇 0x28987 𨦈 0x28988 𨦺 0x289ba 𨦻 0x289bb 𨨞 0x28a1e 𨨩 0x28a29 𨩃 0x28a43 𨩱 0x28a71 𨪙 0x28a99 𨫍 0x28acd
𨫝 0x28add 𨫤 0x28ae4 𨯁 0x28bc1 𨯯 0x28bef 𨳝 0x28cdd 𨴐 0x28d10 𨵱 0x28d71 𨷻 0x28dfb 𨸗 0x28e17 𨸟 0x28e1f
𨸶 0x28e36 𨺉 0x28e89 𨻫 0x28eeb 𨻶 0x28ef6 𨼲 0x28f32 𨿸 0x28ff8 𩊠 0x292a0 𩊱 0x292b1 𩒐 0x29490 𩗏 0x295cf
𩙿 0x2967f 𩛰 0x296f0 𩜙 0x29719 𩝐 0x29750 𩣆 0x298c6 𩩲 0x29a72 𩵋 0x29d4b 𩷛 0x29ddb 𩸕 0x29e15 𩸽 0x29e3d
𩹉 0x29e49 𩺊 0x29e8a 𩻄 0x29ec4 𩻛 0x29edb 𩻩 0x29ee9 𩿎 0x29fce 𩿗 0x29fd7 𪀚 0x2a01a 𪀯 0x2a02f 𪂂 0x2a082
𪃹 0x2a0f9 𪆐 0x2a190 𪊲 0x2a2b2 𪎌 0x2a38c 𪐷 0x2a437 𪗱 0x2a5f1 𪘂 0x2a602 𪘚 0x2a61a 𪚲 0x2a6b2 〓 0x2f804
兔 0x2f80f 再 0x2f815 冤 0x2f818 冬 0x2f81a 割 0x2f822 勺 0x2f828 卉 0x2f82c 卿 0x2f833 周 0x2f83f 善 0x2f846
城 0x2f852 姬 0x2f862 寃 0x2f86d 将 0x2f873 屠 0x2f877 巽 0x2f884 形 0x2f899 彫 0x2f89a 慈 0x2f8a6 憲 0x2f8ac
成 0x2f8b2 拔 0x2f8b6 冕 0x2f8d3 杞 0x2f8db 杓 0x2f8dc 〓 0x2f8e1 〓 0x2f8e5 槪 0x2f8ea 櫛 0x2f8ed 沿 0x2f8fc
浩 0x2f903 滋 0x2f90b 潮 0x2f90f 炭 0x2f91a 爨 0x2f920 爵 0x2f921 眞 0x2f945 真 0x2f947 絣 0x2f96c 芽 0x2f995
諭 0x2f9d0 〓 0x2f9de 輸 0x2f9df 〓 0x2f9f4

まとめ

サロゲートペアに関する処理はめんどくさい事ばかりだけどちゃんとやること。

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)