• WEB

UTF-8の符号化方法について

  • おさ
    おさ システムちーむ
  • このエントリーをはてなブックマークに追加
UTF-8の符号化方法について

テキストファイルを保存するときに何気なく目にする「UTF-8」とかいう文字コードが、実際にはどのような仕組み?仕様?で動いているのかが気になったので調べてみました。
タマに目にするUnicodeというのも絡めて書いていきます。UTF-8の符号化を行う簡単なコードも書いてみます。

UnicodeとUTF-8

Unicode

文字コードの規格。どのような文字があるか(文字集合)や、どのようにデータとして変換するか(符号化)などを決めている。Unicodeに含まれている全ての文字は、コードポイント(符号位置)というIDみたいなものが割り振られている。

UTF-8

Unicodeで定められている符号化方式(文字をコンピュータで扱えるデータに変換するための方式)の一つ。他にUTF-16、UTF-32等の符号化方式がある。

Unicodeと各符号化方式

Unicodeでは、「あ」という文字に「U+3042」というコードポイントを与えています。これを実際にコンピュータ上で扱うには「e38182」というデータに変換します(UTF-8を使用する場合)。

コードポイントと、各符号化方式で符号化したデータの比較は以下のようになります。(※UTF-16、UTF-32はビッグエンディアン)

文字 コードポイント UTF-8 UTF-16 UTF-32
1 U+0031 31 00 31 00 00 00 31
A U+0041 41 00 41 00 00 00 41
Ă U+0102 c4 82 01 02 00 00 01 02
U+3042 e3 81 82 30 42 00 00 30 42
U+4E08 e4 b8 88 4e 08 00 00 4e 08
U+1D49C f0 9d 92 9c d8 35 dc 9c 00 01 d4 9c
U+2000B f0 a0 80 8b d8 40 dc 0b 00 02 00 0b

UTF-32はコードポイントをそのまま4バイト(16進数で8桁)にしたものです。変換方法がとても単純ですがデータ量が多くなるのであまり使用されていません。

UTF-16は2バイト(16進数で4桁)のデータ(1、A、Ă、あ、丈)と4バイトのデータ(、)があります。コードポイントが4桁までの文字は2バイトになり、5桁以上の文字は4バイトになります(サロゲートペアという仕組みを使用する)。

UTF-8では、文字が1バイトから4バイトまでのデータに変換されます。他の方式と比べて変換方法が複雑そうに見えます。一般的に利用されています。

UTF-8の符号化方法

UTF-8が実際にどのような符号化方法を行っているのかを見るために、先ほどの表で出てきたUTF-8のデータの各バイトをビット列に変換してみます。

文字 UTF-8のデータ バイト 1バイト目 2バイト目 3バイト目 4バイト目
1 31 1byte 00110001
A 41 1byte 01000001
Ă c4 82 2byte 11000100 10000010
e3 81 82 3byte 11100011 10000001 10000010
e4 b8 88 3byte 11100100 10111000 10001000
f0 9d 92 9c 4byte 11110000 10011101 10010010 10011100
f0 a0 80 8b 4byte 11110000 10100000 10000000 10001011

赤い部分はUTF-8での固定部分で各文字で共通になります。青い部分は文字ごとに変化します。UTF-8で符号化したデータには以下の規則があります。

  • 1バイト文字 = 先頭ビットが0で固定
  • 2バイト文字 = 1バイト目は「110」から始まり、2バイト目は「10」から始まる
  • 3バイト文字 = 1バイト目は「1110」から始まり、2バイト目以降は「10」から始まる
  • 4バイト文字 = 1バイト目は「11110」から始まり、2バイト目以降は「10」から始まる

この表の青い部分をそれぞれつなげてみます。つなげる時に、バイト単位(8ビット)にするため足りないビットを補います。
それを16進数に変換したものとコードポイントを比較すると一致することが分ります。

文字 青い部分をつなげて足りないビットを補う 16進数に変換 コードポイント
1 00110001 31 U+0031
A 01000001 41 U+0041
Ă 00000001 00000010 01 02 U+0102
00110000 01000010 30 42 U+3042
01001110 00001000 4e 08 U+4E08
00000001 11010100 10011100 01 c4 9c U+1D49C
00000010 00000000 00001011 02 00 0b U+2000B

UnicodeからUTF-8に変換するには、逆の操作を行います。

コードポイントをUTF-8に変換するときに、何バイトの文字になるかはコードポイントの範囲で決まります。UTF-8では固定ビットが存在するので、有効ビット数の範囲内で文字を表現できます。

UTF-8のバイト数 1byte目 2byte目 3byte目 4byte目 有効ビット数 コードポイントの範囲
1byte文字 00000000 7 U+0000~U+007F
2byte文字 11000000 10000000 11 U+0080~U+07FF
3byte文字 11100000 10000000 10000000 16 U+0800~U+FFFF
4byte文字 11110000 10000000 10000000 10000000 21 U+10000~U+10FFFF

プログラム化してみる

上記の仕様をPHPでプログラムしてみました。
UTF-8の文字からコードポイントを取得します。

/**
* UTF-8文字からコードポイントを取得する
* @param string UTF-8文字
* @return string U+xxxx
*/
function utf8ToCp($str)
{
// 文字列だけを扱う
if (!is_string($str)) {
return false;
}
// 先頭1文字だけが対象
$ch = mb_substr($str, 0, 1, 'UTF-8');
if ($ch === '') {
return false;
}
// 16進数のコードに変換
$chHex = bin2hex($ch);

// 長さで分ける
if (strlen($chHex) <= 2) { // 1バイト文字 $byte1 = sprintf('%08s', base_convert($chHex, 16, 2)); // 先頭ビットの確認 // UTF-8の仕様に合わない場合はfalseを返す if (0 !== strpos($byte1, '0')) { return false; } // コードポイントのビット表現 $bin = $byte1; } elseif (strlen($chHex) == 4) { // 2バイト文字 $byte1 = base_convert(substr($chHex, 0, 2), 16, 2); $byte2 = base_convert(substr($chHex, 2, 2), 16, 2); if (0 !== strpos($byte1, '110') || 0 !== strpos($byte2, '10')) { return false; } $bin = substr($byte1, 3, 5) . substr($byte2, 2, 6); } elseif (strlen($chHex) == 6) { // 3バイト文字 $byte1 = base_convert(substr($chHex, 0, 2), 16, 2); $byte2 = base_convert(substr($chHex, 2, 2), 16, 2); $byte3 = base_convert(substr($chHex, 4, 2), 16, 2); if (0 !== strpos($byte1, '1110') || 0 !== strpos($byte2, '10') || 0 !== strpos($byte3, '10')) { return false; } $bin = substr($byte1, 4, 4) . substr($byte2, 2, 6) . substr($byte3, 2, 6); } elseif (strlen($chHex) == 8) { // 4バイト文字 $byte1 = base_convert(substr($chHex, 0, 2), 16, 2); $byte2 = base_convert(substr($chHex, 2, 2), 16, 2); $byte3 = base_convert(substr($chHex, 4, 2), 16, 2); $byte4 = base_convert(substr($chHex, 6, 2), 16, 2); if (0 !== strpos($byte1, '11110') || 0 !== strpos($byte2, '10') || 0 !== strpos($byte3, '10') || 0 !== strpos($byte4, '10')) { return false; } $bin = substr($byte1, 5, 3) . substr($byte2, 2, 6) . substr($byte3, 2, 6) . substr($byte4, 2, 6); } else { return false; } // コードポイントを16進数に変換 $cpHex = sprintf('%04s', base_convert($bin, 2, 16)); return "U+{$cpHex}"; } // 実行する echo utf8ToCp('A'); ==> U+0041

echo utf8ToCp('¶');
==> U+00b6

echo utf8ToCp('あ');
==> U+3042

今度はコードポイントからUTF-8に変換します。

/**
* コードポイントからUTF-8に変換す
* @param string U+xxxx
* @return string UTF-8文字
*/
function cpToUtf8($cp)
{
// 数字部分だけ抜き出す
if (!preg_match('/^U+([0-9a-fA-F]{1,6})$/', $cp, $matches)) {
return false;
}
$cpHex = $matches[1];
$cpDec = hexdec($cpHex);

// コードポイントの範囲でバイト数を決定
if ($cpDec <= 0x7f) {
// 1バイト文字(有効桁数7ビット)
$cpBin = base_convert($cpHex, 16, 2);
$bin = sprintf('%08s', $cpBin);
} elseif ($cpDec <= 0x7ff) {
// 2バイト文字(有効桁数11ビット)
$cpBin = sprintf('%011s', base_convert($cpHex, 16, 2));
$bin = sprintf('110%05s', substr($cpBin, 0, 5))
. sprintf('10%06s', substr($cpBin, 5, 6));
} elseif ($cpDec <= 0xffff) {
// 3バイト文字(有効桁数16ビット)
$cpBin = sprintf('%016s', base_convert($cpHex, 16, 2));
$bin = sprintf('1110%04s', substr($cpBin, 0, 4))
. sprintf('10%06s', substr($cpBin, 4, 6))
. sprintf('10%06s', substr($cpBin, 10, 6));
} elseif ($cpDec <= 0x10ffff) { // 4バイト文字(有効桁数21ビット) $cpBin = sprintf('%021s', base_convert($cpHex, 16, 2)); $bin = sprintf('11110%03s', substr($cpBin, 0, 3)) . sprintf('10%06s', substr($cpBin, 3, 6)) . sprintf('10%06s', substr($cpBin, 9, 6)) . sprintf('10%06s', substr($cpBin, 15, 6)); } else { return false; } $hex = base_convert($bin, 2, 16); $char = pack('H*', $hex); return $char; } // 実行する echo cpToUtf8('U+41'); ==> A

echo cpToUtf8('U+3042');
==> あ

PHPでは文字とHTML文字参照を相互に変換する関数が用意されているので、実際のプログラムではそれを利用するのがラクでしょう。

// 文字を文字参照に変換する
echo mb_encode_numericentity('あいう', [0x0, 0x10ffff, 0, 0xffffff], 'UTF-8', true);
==> あいう

// 文字参照を文字に変換する
echo mb_decode_numericentity('あいう', [0x0, 0x10ffff, 0, 0xffffff], 'UTF-8');
==> あいう

参考

UTF-8
UTF-8 の文字からコードポイントを求める

このエントリーをはてなブックマークに追加

おさが最近書いた記事

WRITERS POSTS もっと見る

他にもこんな記事が読まれています!

  • WEB
  • マーケティング
  • サーバー・ネットワーク
  • ライフスタイル
  • お知らせ