Comment un fichier avec des caractères chinois sait - il combien d'octets utiliser par caractère?

j'ai lu L'article de Joël "le Minimum absolu tout développeur de logiciel doit absolument, positivement connaître Unicode et les jeux de caractères (pas D'Excuses!)" , mais encore ne pas comprendre tous les détails. Un exemple illustrera mes problèmes. Regardez ce fichier ci-dessous:

alt texte http://www.yart.com.au/stackoverflow/unicode2.png

j'ai ouvert le fichier dans un éditeur binaire de près examiner le dernier des trois d'un côté du premier caractère Chinois:

alt texte http://www.yart.com.au/stackoverflow/unicode1.png

selon Joël:

en UTF-8, chaque point de code 0-127 est stocké dans un seul octet. Seuls les points de code 128 et plus sont stockés en utilisant 2, 3, en fait, jusqu'à 6 octets.

ainsi que le dit le rédacteur en chef:

  1. E6 (230) est au-dessus du point de code 128.
  2. Donc je vais interpréter les suivantes octets soit 2, 3, en fait, jusqu'à 6 octets.

si oui, qu'est-ce qui indique que l'interprétation est de plus de 2 octets? Comment cela est-il indiqué par les octets qui suivent E6?

est-ce que mon caractère chinois est stocké en 2, 3, 4, 5 ou 6 octets?

20
demandé sur Makoto 2009-04-22 05:40:08

9 réponses

si L'encodage est UTF-8, alors le tableau suivant montre comment un point de code Unicode (jusqu'à 21 bits) est converti en encodage 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

il y a un certain nombre de valeurs non autorisées-en particulier, les octets 0xC1, 0xC2, et 0xF5 - 0xFF ne peuvent jamais apparaître dans L'UTF - 8 bien formé. Il existe également un certain nombre d'autres combinaisons verboten. Les irrégularités se trouvent dans les colonnes 1 et 2. Notez que les codes U+D800-U+DFFF sont réservés pour UTF-16 les substituts et ne peuvent pas figurer dans L'UTF-8 valide.

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

ces tableaux sont tirés du Unicode standard version 5.1.


dans la question, le matériel de offset 0x0010 .. 0x008f rendement:

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
28
répondu Jonathan Leffler 2009-04-23 06:35:06

Cela fait partie de L'encodage UTF8 (qui n'est qu'un schéma d'encodage pour Unicode).

La taille peut compris en examinant le premier octet comme suit:

  • si elle commence avec le motif de bits "10" (0x80-0xbf) , ce n'est pas le premier octet d'une séquence et vous devriez sauvegarder jusqu'à ce que vous trouviez le début, tout octet qui commence par "0" ou " 11 " (Merci à Jeffrey Hantin de le souligner dans les commentaires).
  • si ça commence avec un motif de bits "0" (0x00-0x7f) , c'est 1 octet.
  • si ça commence avec un motif de bits "110" (0xc0-0xdf) , c'est 2 octets.
  • si ça commence avec un motif de bits "1110" (0xe0-0xef) , c'est 3 octets.
  • si ça commence avec un motif de bits "11110" (0xf0-0xf7) , c'est 4 octets.

je vais dupliquer le tableau montrant ceci, mais l'original est sur la page de Wikipedia UTF8 ici .

+----------------+----------+----------+----------+----------+
| 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 |
+----------------+----------+----------+----------+----------+

les caractères Unicode dans le tableau ci-dessus sont construits à partir des bits:

000z-zzzz yyyy-yyyy xxxx-xxxx

où les bits z et y sont supposés être zéro où ils ne sont pas donnés. Certains octets sont considérés comme illégaux en tant qu'octets de départ puisqu'ils sont soit:

  • inutile: une séquence de 2 octets commençant par 0xc0 ou 0xc1 donne en fait un point de code inférieur à 0x80 qui peut être représenté mieux avec une séquence d'un octet.
  • utilisé par RFC3629 pour les séquences de 4 octets au-dessus de U+10FF, ou les séquences de 5 octets et 6 octets. Ce sont les octets 0xf5 à 0xfd.
  • juste utilisé: octets 0xfe et 0xff.

en outre, les octets suivants dans une séquence de plusieurs octets qui ne commence pas par les bits " 10 " sont également illégaux.

à titre d'exemple, considérons la séquence [0xf4,0x8a,0xaf,0x8d]. C'est un La séquence de 4 octets comme premier octet tombe entre 0xf0 et 0xf7.

    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

Pour votre requête spécifique avec le premier octet 0xe6 (longueur = 3), la séquence d'octets est:

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

= 01101111 10110011
  yyyyyyyy xxxxxxxx

= U+6FB3

si vous regardez ce code vers le haut ici , vous verrez que c'est celui que vous aviez dans votre question: փ.

pour montrer comment le décodage fonctionne, je suis retourné à mes archives pour trouver mon code de manipulation UTF8. J'ai eu à métamorphoser un peu de faire un programme complet et l'encodage a été supprimé( puisque la question était vraiment sur le décodage), donc j'espère que je n'ai pas introduit d'erreurs de la coupe et coller:

#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 != '"151940920"') {
        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;
}

vous pouvez l'exécuter avec votre séquence d'octets (vous aurez besoin de 4 donc utilisez 0 pour les remplir) comme suit:

> 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
22
répondu paxdiablo 2010-01-13 04:45:58

une excellente référence est le UTF-8 DE Markus Kuhn et le D'Unicode FAQ .

5
répondu Greg Hewgill 2009-04-22 01:50:41

essentiellement, si elle commence par un 0, c'est un point de code 7 bits. Si elle commence à 10, c'est la suite d'un codepoint multi-octets. Sinon, le nombre de 1 vous indique combien d'octets ce point de code est encodé.

le premier octet indique combien d'octets encodent le point de code.

0xxxxxxx 7 bits de point de code codé en 1 octets

110xxxxx 10xxxxxx 10 bits de point de code codé sur 2 octets

110xxxxx 10xxxxxx 10xxxxxx etc. 1110xxxx 11110xxx etc.

3
répondu caskey 2009-04-22 01:48:00
2
répondu ja. 2009-04-22 01:55:44

UTF-8 est construit de manière à ce qu'il n'y ait aucune ambiguïté possible sur l'endroit où un caractère commence et le nombre d'octets qu'il contient.

c'est très simple.

  • Un octet dans la gamme 0x80 à 0xBF est jamais le premier octet d'un caractère.
  • Tout autre octet est toujours le premier octet d'un caractère.

UTF-8 a beaucoup de redondance.

si vous voulez dire combien d'octets est un caractère, il y a plusieurs façons de le dire.

  • le premier octet vous dit toujours combien d'octets le caractère est long:
    • si le premier octet est 0x00 à 0x7F, c'est un octet.
    • 0xC2 à 0xDF signifie deux octets.
    • 0xE0 à 0xEF signifie que c'est trois octet.
    • 0xF0 à 0xF4 signifie quatre octets.
  • ou, vous pouvez simplement compter le nombre d'octets consécutifs dans la gamme de 0x80 à 0xBF, parce que ces octets appartiennent tous au même caractère que le octet précédent.

certains octets ne sont jamais utilisés, comme 0xC1 à 0xC2 ou 0xF5 à 0xFF, donc si vous rencontrez ces octets n'importe où, alors vous ne regardez pas UTF-8.

2
répondu thomasrutter 2009-04-22 05:08:48

points de Code jusqu'à 0x7ff est stocké en 2 octets; jusqu'à 0xffff en 3 octets; tout le reste en 4 octets. (Techniquement, jusqu'à 0x1ffff, mais le point le plus élevé autorisé en Unicode est 0x10ff.)

lors du décodage, le premier octet de la séquence multi-octets est utilisé pour déterminer le nombre d'octets utilisés pour faire la séquence:

  1. 110x xxxx => 2-séquence d'octets
  2. 1110 xxxx => 3-séquence d'octets
  3. 1111 0xxx => 4-séquence d'octets

tous les octets suivants dans la séquence doivent correspondre au motif 10xx xxxx .

2
répondu Chris Jester-Young 2009-04-23 02:32:05

l'allusion est dans cette phrase ici:

en UTF-8, chaque point de code de 0-127 est stocké dans un octet. Seul le code les points 128 et supérieurs sont stockés en utilisant 2, 3, en fait, jusqu'à 6 octets.

chaque point de code jusqu'à 127 a le bit supérieur réglé à zéro. Par conséquent, l'éditeur sait que s'il rencontre un octet où le bit supérieur est un 1, c'est le début d'un caractère multi-octets.

1
répondu 1800 INFORMATION 2009-04-22 01:53:18

pourquoi y a-t-il tant de réponses compliquées?

3 octets pour 1 caractère chinois. en utilisant cette fonction (sous jQuery):

function get_length(field_selector) {
  var escapedStr = encodeURI($(field_selector).val())
  if (escapedStr.indexOf("%") != -1) {
    var count = escapedStr.split("%").length - 1
    if (count == 0) count++  //perverse case; can't happen with real UTF-8
    var tmp = escapedStr.length - (count * 3)
    count = count + tmp
  } else {
    count = escapedStr.length
  }
  return count
}
0
répondu Siwei Shen申思维 2013-01-04 07:53:45