UTF-8 の体系
UTF-8では、Unicode文字を 1~4 バイトの可変長で表現する符号化方式です。
U+0000
から U+007f
つまり、0-127 の範囲は US-ASCII と互換性があります。
各Unicode範囲において以下のような割当となります。
Unicode | 1 | 2 | 3 | 4 |
---|---|---|---|---|
U+0000 - U+007f |
0xxx-xxxx |
|||
U+0080 - U+07ff |
110x-xxxx |
10xx-xxxx |
||
U+0800 - U+ffff |
1110-xxxx |
10xx-xxxx |
10xx-xxxx |
|
U+10000 - U+10ffff |
1111-0xxx |
10xx-xxxx |
10xx-xxxx |
10xx-xxxx |
先頭バイトのビットパターンを見れば、続くバイト数が判別できるようになっています。
下位バイトは先頭が 10
のビットパターンになります。
なお、UTF-8で符号されたテキストデータにBOM(Byte Order Mark)は不要ですが、先頭に 0xEF 0xBB 0xBF (11101111 10111011 10111111) としてBOMを付加する場合もあります。 ただし通常 BOM を付与するケースはないでしょう。
UTF-8 エンコード
U+3042
「あ」 を例にとり、UTF-8 のエンコードについて見ていきましょう。
U+3042
は U+0800
- U+ffff
の範囲に該当するため UTF-8 では3バイトで表現します。
U+3042
を2進数で表すと以下になります。
0011 0000 0100 0010
下位から6ビットずつ分割すると以下のようになります。
0011 000001 000010
それぞれ定められたビットパターン 1110
10
10
を付与します。
1110 0011 10 000001 10 000010 ↓ 1110 0011 1000 0001 1000 0010
これで UTF-8 エンコード が完了し、16進で e3 81 82
となります。
Java では以下のように書くことができます。
public static byte[] toUtf8(int cp) { int mask = 0x3F; // 6bit mask 0011 1111 if (0x0000 <= cp && cp <= 0x007f) { return new byte[] { (byte) cp }; } else if (0x0080 <= cp && cp <= 0x07ff) { return new byte[] { (byte) (0xC0 | (cp >>> 6)), (byte) (0x80 | (cp & mask)) }; } else if (0x0800 <= cp && cp <= 0xffff) { return new byte[] { (byte) (0xE0 | (cp >>> 12)), (byte) (0x80 | (cp >>> 6 & mask)), (byte) (0x80 | (cp & mask)) }; } else if (0x10000 <= cp && cp <= 0x10ffff) { return new byte[] { (byte) (0xF0 | (cp >>> 18)), (byte) (0x80 | (cp >>> 12 & mask)), (byte) (0x80 | (cp >>> 6 & mask)), (byte) (0x80 | (cp & mask)) }; } else { throw new IllegalStateException("Illegal code point. " + cp); } }
UTF-8 のバイト判定
UTF-8 では、先頭バイトを見ることで続くバイト数が判断できます。
0xxx-xxxx
: 1バイト110x-xxxx
: 2バイト1110-xxxx
: 3バイト1111-0xxx
: 4バイト
バイト列の先頭のビットパターンを使えば、それに続くバイト数を判定することができます。
public static short countUtf8Byte(byte b) { if ((b & 0x80) == 0x00) { return 1; } else if ((b & 0xE0) == 0xC0) { return 2; } else if ((b & 0xF0) == 0xE0) { return 3; } else if ((b & 0xF8) == 0xF0) { return 4; } else { return 0; } }
byte のシフト演算は int にワイドニング変換されるので、シフト演算ではなくビット論理演算を利用します。
特定バイトが UTF-8 の下位バイトかどうかは、先頭ビットが 10
(10xx-xxxx
) で始まるかを見れば良いので以下のように判断できます。
public static boolean isUtf8Lower(byte b) { return (b & 0xC0) == 0x80; }