21

Joel の記事「すべてのソフトウェア開発者が絶対に、積極的に Unicode と文字セットについて知っておく必要がある絶対的な最小値 (言い訳はありません!)」を読みましたが、まだすべての詳細を理解していません。例は私の問題を説明します。以下のファイルを見てください。

代替テキスト
(ソース: yart.com.au )

バイナリ エディタでファイルを開き、最初の漢字の隣にある 3 つの a の最後の文字を詳しく調べました。

代替テキスト
(ソース: yart.com.au )

ジョエルによると:

UTF-8 では、0 ~ 127 のすべてのコード ポイントが 1 バイトに格納されます。128 以上のコード ポイントのみが 2、3、実際には最大 6 バイトを使用して格納されます。

編集者は次のように述べています。

  1. E6 (230) はコード ポイント 128 より上です。
  2. したがって、次のバイトを 2、3、実際には最大 6 バイトとして解釈します。

もしそうなら、解釈が 2 バイト以上であることを示すものは何ですか? これは、E6 に続くバイトによってどのように示されますか?

漢字は 2、3、4、5、または 6 バイトで保存されますか?

4

9 に答える 9

28

エンコーディングが UTF-8 の場合、次の表は Unicode コード ポイント (最大 21 ビット) が UTF-8 エンコーディングに変換される方法を示しています。

Scalar Value                 1st Byte  2nd Byte  3rd Byte  4th Byte
00000000 0xxxxxxx            0xxxxxxx
00000yyy yyxxxxxx            110yyyyy  10xxxxxx
zzzzyyyy yyxxxxxx            1110zzzz  10yyyyyy  10xxxxxx
000uuuuu zzzzyyyy  yyxxxxxx  11110uuu  10uuzzzz  10yyyyyy  10xxxxxx

許可されていない値がいくつかあります。特に、バイト 0xC1、0xC2、および 0xF5 - 0xFF は整形式の UTF-8 には表示されません。他にも多くの禁止された組み合わせがあります。1 バイト目と 2 バイト目の列に凹凸があります。コード U+D800 - U+DFFF は UTF-16 サロゲート用に予約されており、有効な UTF-8 では表示できないことに注意してください。

Code Points          1st Byte  2nd Byte  3rd Byte  4th Byte
U+0000..U+007F       00..7F
U+0080..U+07FF       C2..DF    80..BF
U+0800..U+0FFF       E0        A0..BF    80..BF
U+1000..U+CFFF       E1..EC    80..BF    80..BF
U+D000..U+D7FF       ED        80..9F    80..BF
U+E000..U+FFFF       EE..EF    80..BF    80..BF
U+10000..U+3FFFF     F0        90..BF    80..BF    80..BF
U+40000..U+FFFFF     F1..F3    80..BF    80..BF    80..BF
U+100000..U+10FFFF   F4        80..8F    80..BF    80..BF

これらのテーブルは、Unicode標準バージョン 5.1 から取り除かれました。


問題では、オフセット 0x0010 .. 0x008F からのマテリアルは次のようになります。

0x61           = U+0061
0x61           = U+0061
0x61           = U+0061
0xE6 0xBE 0xB3 = U+6FB3
0xE5 0xA4 0xA7 = U+5927
0xE5 0x88 0xA9 = U+5229
0xE4 0xBA 0x9A = U+4E9A
0xE4 0xB8 0xAD = U+4E2D
0xE6 0x96 0x87 = U+6587
0xE8 0xAE 0xBA = U+8BBA
0xE5 0x9D 0x9B = U+575B
0x2C           = U+002C
0xE6 0xBE 0xB3 = U+6FB3
0xE6 0xB4 0xB2 = U+6D32
0xE8 0xAE 0xBA = U+8BBA
0xE5 0x9D 0x9B = U+575B
0x2C           = U+002C
0xE6 0xBE 0xB3 = U+6FB3
0xE6 0xB4 0xB2 = U+6D32
0xE6 0x96 0xB0 = U+65B0
0xE9 0x97 0xBB = U+95FB
0x2C           = U+002C
0xE6 0xBE 0xB3 = U+6FB3
0xE6 0xB4 0xB2 = U+6D32
0xE4 0xB8 0xAD = U+4E2D
0xE6 0x96 0x87 = U+6587
0xE7 0xBD 0x91 = U+7F51
0xE7 0xAB 0x99 = U+7AD9
0x2C           = U+002C
0xE6 0xBE 0xB3 = U+6FB3
0xE5 0xA4 0xA7 = U+5927
0xE5 0x88 0xA9 = U+5229
0xE4 0xBA 0x9A = U+4E9A
0xE6 0x9C 0x80 = U+6700
0xE5 0xA4 0xA7 = U+5927
0xE7 0x9A 0x84 = U+7684
0xE5 0x8D 0x8E = U+534E
0x2D           = U+002D
0x29           = U+0029
0xE5 0xA5 0xA5 = U+5965
0xE5 0xB0 0xBA = U+5C3A
0xE7 0xBD 0x91 = U+7F51
0x26           = U+0026
0x6C           = U+006C
0x74           = U+0074
0x3B           = U+003B
于 2009-04-22T01:49:19.003 に答える
22

これはすべて UTF8 エンコーディングの一部です (これは Unicode の唯一のエンコーディング スキームです)。

サイズは、次のように最初のバイトを調べることで把握できます。

  • ビットパターンで始まる場合、それ"10" (0x80-0xbf)はシーケンスの最初のバイトではないため、「0」または「11」で始まるバイトが見つかるまでバックアップする必要があります(コメントで指摘してくれた Jeffrey Hantin に感謝します) )。
  • bit pattern で始まる場合は"0" (0x00-0x7f)1 バイトです。
  • bit pattern で始まる場合は"110" (0xc0-0xdf)2 バイトです。
  • bit pattern で始まる場合は"1110" (0xe0-0xef)3 バイトです。
  • bit pattern で始まる場合は"11110" (0xf0-0xf7)4 バイトです。

これを示す表を複製しますが、オリジナルはウィキペディアの UTF8 ページ (こちら) にあります。

+----------------+----------+----------+----------+----------+
| Unicode        | Byte 1   | Byte 2   | Byte 3   | Byte 4   |
+----------------+----------+----------+----------+----------+
| U+0000-007F    | 0xxxxxxx |          |          |          |
| U+0080-07FF    | 110yyyxx | 10xxxxxx |          |          |
| U+0800-FFFF    | 1110yyyy | 10yyyyxx | 10xxxxxx |          |
| U+10000-10FFFF | 11110zzz | 10zzyyyy | 10yyyyxx | 10xxxxxx |
+----------------+----------+----------+----------+----------+

上記の表の Unicode 文字は、次のビットから構成されます。

000z-zzzz yyyy-yyyy xxxx-xxxx

zおよびビットがy指定されていない場合はゼロと見なされます。一部のバイトは、次のいずれかであるため、開始バイトとして不正と見なされます。

  • 役に立たない: 0xc0 または 0xc1 で始まる 2 バイトのシーケンスは、実際には、1 バイトのシーケンスでより適切に表現できる 0x80 未満のコード ポイントを与えます。
  • U+10FFFF を超える 4 バイト シーケンス、または 5 バイトおよび 6 バイト シーケンスに RFC3629 で使用されます。これらはバイト 0xf5 から 0xfd です。
  • 未使用: バイト 0xfe と 0xff。

さらに、ビット「10」で始まらないマルチバイト シーケンス内の後続のバイトも不正です。

例として、シーケンス [0xf4,0x8a,0xaf,0x8d] を考えてみましょう。最初のバイトが 0xf0 と 0xf7 の間にあるため、これは 4 バイトのシーケンスです。

    0xf4     0x8a     0xaf     0x8d
= 11110100 10001010 10101111 10001101
       zzz   zzyyyy   yyyyxx   xxxxxx

= 1 0000 1010 1011 1100 1101
  z zzzz yyyy yyyy xxxx xxxx

= U+10ABCD

最初のバイトが 0xe6 (長さ = 3) の特定のクエリの場合、バイト シーケンスは次のようになります。

    0xe6     0xbe     0xb3
= 11100110 10111110 10110011
      yyyy   yyyyxx   xxxxxx

= 01101111 10110011
  yyyyyyyy xxxxxxxx

= U+6FB3

ここでそのコードを見ると、それがあなたの質問にあったものであることがわかります: 澳.

デコードがどのように機能するかを示すために、アーカイブに戻って UTF8 処理コードを見つけました。完全なプログラムにするために少し変形する必要があり、エンコーディングは削除されました (質問は実際にはデコードに関するものだったため)。切り取りと貼り付けでエラーが発生していないことを願っています。

#include <stdio.h>
#include <string.h>

#define UTF8ERR_TOOSHORT -1
#define UTF8ERR_BADSTART -2
#define UTF8ERR_BADSUBSQ -3
typedef unsigned char uchar;

static int getUtf8 (uchar *pBytes, int *pLen) {
    if (*pLen < 1) return UTF8ERR_TOOSHORT;

    /* 1-byte sequence */
    if (pBytes[0] <= 0x7f) {
        *pLen = 1;
        return pBytes[0];
    }

    /* Subsequent byte marker */
    if (pBytes[0] <= 0xbf) return UTF8ERR_BADSTART;

    /* 2-byte sequence */
    if ((pBytes[0] == 0xc0) || (pBytes[0] == 0xc1)) return UTF8ERR_BADSTART;
    if (pBytes[0] <= 0xdf) {
        if (*pLen < 2) return UTF8ERR_TOOSHORT;
        if ((pBytes[1] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
        *pLen = 2;
        return ((int)(pBytes[0] & 0x1f) << 6)
            | (pBytes[1] & 0x3f);
    }

    /* 3-byte sequence */
    if (pBytes[0] <= 0xef) {
        if (*pLen < 3) return UTF8ERR_TOOSHORT;
        if ((pBytes[1] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
        if ((pBytes[2] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
        *pLen = 3;
        return ((int)(pBytes[0] & 0x0f) << 12)
            | ((int)(pBytes[1] & 0x3f) << 6)
            | (pBytes[2] & 0x3f);
    }

    /* 4-byte sequence */
    if (pBytes[0] <= 0xf4) {
        if (*pLen < 4) return UTF8ERR_TOOSHORT;
        if ((pBytes[1] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
        if ((pBytes[2] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
        if ((pBytes[3] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
        *pLen = 4;
        return ((int)(pBytes[0] & 0x0f) << 18)
            | ((int)(pBytes[1] & 0x3f) << 12)
            | ((int)(pBytes[2] & 0x3f) << 6)
            | (pBytes[3] & 0x3f);
    }

    return UTF8ERR_BADSTART;
}

static uchar htoc (char *h) {
    uchar u = 0;
    while (*h != '\0') {
        if ((*h >= '0') && (*h <= '9'))
            u = ((u & 0x0f) << 4) + *h - '0';
        else
            if ((*h >= 'a') && (*h <= 'f'))
                u = ((u & 0x0f) << 4) + *h + 10 - 'a';
            else
                return 0;
        h++;
    }
    return u;
}

int main (int argCount, char *argVar[]) {
    int i;
    uchar utf8[4];
    int len = argCount - 1;

    if (len != 4) {
            printf ("Usage: utf8 <hex1> <hex2> <hex3> <hex4>\n");
            return 1;
    }
    printf ("Input:      (%d) %s %s %s %s\n",
        len, argVar[1], argVar[2], argVar[3], argVar[4]);

    for (i = 0; i < 4; i++)
            utf8[i] = htoc (argVar[i+1]);

    printf ("   Becomes: (%d) %02x %02x %02x %02x\n",
        len, utf8[0], utf8[1], utf8[2], utf8[3]);

    if ((i = getUtf8 (&(utf8[0]), &len)) < 0)
        printf ("Error %d\n", i);
    else
        printf ("   Finally: U+%x, with length of %d\n", i, len);

    return 0;
}

次のように、一連のバイトで実行できます(4が必要なので、0を使用してパディングします):

> utf8 f4 8a af 8d
Input:      (4) f4 8a af 8d
   Becomes: (4) f4 8a af 8d
   Finally: U+10abcd, with length of 4

> utf8 e6 be b3 0
Input:      (4) e6 be b3 0
   Becomes: (4) e6 be b3 00
   Finally: U+6fb3, with length of 3

> utf8 41 0 0 0
Input:      (4) 41 0 0 0
   Becomes: (4) 41 00 00 00
   Finally: U+41, with length of 1

> utf8 87 0 0 0
Input:      (4) 87 0 0 0
   Becomes: (4) 87 00 00 00
Error -2

> utf8 f4 8a af ff
Input:      (4) f4 8a af ff
   Becomes: (4) f4 8a af ff
Error -3

> utf8 c4 80 0 0
Input:      (4) c4 80 0 0
   Becomes: (4) c4 80 00 00
   Finally: U+100, with length of 2
于 2009-04-22T01:46:56.247 に答える
5

これに関する優れたリファレンスは、Markus Kuhn のUTF-8 and Unicode FAQです。

于 2009-04-22T01:50:41.807 に答える
3

基本的に、0 で始まる場合は 7 ビットのコード ポイントです。10 で始まる場合は、マルチバイト コードポイントの続きです。それ以外の場合、1 の数は、このコード ポイントがエンコードされているバイト数を示します。

最初のバイトは、コード ポイントをエンコードするバイト数を示します。

0xxxxxxx 1 バイトにエンコードされた 7 ビットのコード ポイント

110xxxxx 10xxxxxx 2 バイトにエンコードされた 10 ビットのコード ポイント

110xxxxx 10xxxxxx 10xxxxxx など 1110xxxx 11110xxx など

于 2009-04-22T01:48:00.003 に答える
2

UTF-8は、文字の開始位置と文字数についてあいまいさが生じないように構成されています。

とても簡単です。

  • 0x80から0xBFの範囲のバイトは、文字の最初のバイトになることはありません。
  • 他のバイトは常に文字の最初のバイトです。

UTF-8には多くの冗長性があります。

文字の長さが何バイトかを知りたい場合は、複数の方法で判断できます。

  • 最初のバイトは常に、文字の長さが何バイトかを示します。
    • 最初のバイトが0x00から0x7Fの場合、それは1バイトです。
    • 0xC2から0xDFは、2バイトであることを意味します。
    • 0xE0から0xEFは、3バイトであることを意味します。
    • 0xF0から0xF4は、4バイトであることを意味します。
  • または、0x80から0xBFの範囲の連続するバイト数を数えることもできます。これは、これらのバイトがすべて前のバイトと同じ文字に属しているためです。

0xC1から0xC2または0xF5から0xFFのように、一部のバイトは使用されないため、これらのバイトがどこかで検出された場合、UTF-8は表示されません。

于 2009-04-22T05:08:48.430 に答える
2

0x7ff までのコード ポイントは 2 バイトとして格納されます。3 バイトとして 0xffff まで。それ以外はすべて 4 バイト。(技術的には 0x1fffff までですが、Unicode で許可される最高のコードポイントは 0x10ffff です。)

デコード時には、マルチバイト シーケンスの最初のバイトを使用して、シーケンスの作成に使用されるバイト数が決定されます。

  1. 110x xxxx=> 2 バイト シーケンス
  2. 1110 xxxx=> 3 バイト シーケンス
  3. 1111 0xxx=> 4バイトシーケンス

シーケンス内の後続のすべてのバイトは、10xx xxxxパターンに適合する必要があります。

于 2009-04-22T01:45:48.480 に答える
2

3 バイト
http://en.wikipedia.org/wiki/UTF-8#Description

于 2009-04-22T01:55:44.477 に答える
1

ヒントは次の文にあります。

UTF-8 では、0 ~ 127 のすべてのコード ポイントが 1 バイトに格納されます。128 以上のコード ポイントのみが 2、3、実際には最大 6 バイトを使用して格納されます。

127 までのすべてのコード ポイントの最上位ビットはゼロに設定されています。したがって、エディターは、最上位ビットが 1 であるバイトに遭遇した場合、それがマルチバイト文字の開始であることを認識します。

于 2009-04-22T01:45:03.880 に答える