2009-04-22 18 views
20

He leído el artículo de Joel "The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)" pero todavía no entiendo todos los detalles. Un ejemplo ilustrará mis problemas. Mira este archivo a continuación:¿Cómo sabe un archivo con caracteres chinos cuántos bytes usar por carácter?

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

He abierto el archivo en un editor binario para examinar de cerca la última de las tres A al lado del primer carácter chino:

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

acuerdo para Joel:

En UTF-8, cada punto de código de 0-127 se almacena en un solo byte. Solo los puntos de código 128 y superiores se almacenan usando 2, 3, de hecho, hasta 6 bytes.

Así Qué dice el editor:

  1. E6 (230) está por encima de punto de código 128.
  2. Por lo tanto voy a interpretar los siguientes bytes, ya sea como 2, 3, de hecho, hasta 6 bytes.

Si es así, ¿qué indica que la interpretación es de más de 2 bytes? ¿Cómo se indica esto con los bytes que siguen a E6?

¿Está mi carácter chino almacenado en 2, 3, 4, 5 o 6 bytes?

+1

Los dos bytes que ha resaltado en su editor hexadecimal son dos bytes de un carácter de tres bytes. – thomasrutter

+6

Tenga en cuenta que la información de Joel está un poco desactualizada: UTF-8 usa un máximo de cuatro bytes por carácter, no seis. Originalmente fue diseñado para codificar un potencial de 2^31 caracteres, pero fue reducido en 2003 para manejar solo el rango cubierto por la definición formal de Unicode, U + 0000 a U + 10FFFF. –

Respuesta

28

Si la codificación es UTF-8, la siguiente tabla muestra cómo un código Unicode (hasta 21 bits) se convierte en codificación 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 

Hay una serie de valores no permitidos, en particular, los bytes 0xC1, 0xC2 y 0xF5 - 0xFF nunca pueden aparecer en UTF-8 bien formado. También hay varias otras combinaciones de verboten. Las irregularidades están en las columnas de primer byte y segundo byte. Tenga en cuenta que los códigos U + D800 - U + DFFF están reservados para los sustitutos UTF-16 y no pueden aparecer en el UTF-8 válido.

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 

Estas tablas se levantaron del Unicode versión estándar 5.1.


En la pregunta, el material de desplazamiento 0x0010 .. 0x008F rendimientos:

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 
1

La sugerencia es en esta frase aquí:

En UTF-8, cada punto CÓDIGO de 0-127 se almacena en un solo byte. Solo el código puntos 128 y superiores se almacenan usando 2, 3, de hecho, hasta 6 bytes.

Cada punto de código hasta 127 tiene el bit superior puesto a cero. Por lo tanto, el editor sabe que si encuentra un byte donde el bit superior es un 1, es el comienzo de un carácter de múltiples bytes.

+1

El editor sabe que si encuentra un byte donde los primeros dos bits son 11, es el comienzo de un carácter de múltiples bytes. Si los primeros dos bits son 10, es un byte de continuación de un carácter de múltiples bytes. –

1

Los puntos de código hasta 0x7ff se almacenan como 2 bytes; hasta 0xffff como 3 bytes; todo lo demás como 4 bytes. (Técnicamente, hasta 0x1fffff, pero el más alto punto de código permitido en Unicode es 0x10ffff.)

Al decodificar, el primer byte de la secuencia multi-byte se utiliza para determinar el número de bytes utilizados para hacer la secuencia:

  1. 110x xxxx => 2-byte secuencia
  2. 1110 xxxx => 3-byte secuencia secuencia
  3. 1111 0xxx => 4-byte

Todos los bytes posteriores en la secuencia deben ajustarse al patrón 10xx xxxx.

21

Todo eso es parte de la codificación UTF8 (que es solo un esquema de codificación para Unicode).

El tamaño puede averiguado mediante el examen del primer byte de la siguiente manera:

  • si comienza con el patrón de bits "10" (0x80-0xbf), no es el primer byte de una secuencia y se debe realizar copias de seguridad hasta que encuentre el comienzo, cualquier byte que comience con "0" o "11" (gracias a Jeffrey Hantin por señalarlo en los comentarios).
  • si comienza con el patrón de bits "0" (0x00-0x7f), es de 1 byte.
  • si comienza con el patrón de bits "110" (0xc0-0xdf), son 2 bytes.
  • si comienza con el patrón de bits "1110" (0xe0-0xef), es de 3 bytes.
  • si comienza con el patrón de bits "11110" (0xf0-0xf7), es de 4 bytes.

Voy a duplicar la tabla que muestra esto, pero el original está en la página de Wikipedia UTF8 here.

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

caracteres UNICODE de la tabla anterior se construyen a partir de los bits:

000z-zzzz yyyy-yyyy xxxx-xxxx 

donde se supone que los z y y bits a ser cero en el que no se les da. Algunos bytes son considerados ilegales como un byte de inicio ya que son bien:

  • inútil: una secuencia de 2 bytes a partir de 0xc0 o 0xC1 en realidad le da un punto de código de menos de 0x80 que se puede representar mejor con un 1- secuencia de bytes.
  • utilizado por RFC3629 para una secuencia de 4 bytes por encima de U + 10FFFF, o secuencias de 5 y 6 bytes. Estos son los bytes 0xf5 a 0xfd.
  • no utilizado: bytes 0xfe y 0xff.

Además, los bytes subsiguientes en una secuencia de múltiples bytes que no comienzan con los bits "10" también son ilegales.

Como ejemplo, considere la secuencia [0xf4,0x8a, 0xaf, 0x8d]. Esta es una secuencia de 4 bytes ya que el primer byte cae entre 0xf0 y 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 

Para su consulta específica con el primer 0xE6 bytes (longitud = 3), la secuencia de bytes es:

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

= 01101111 10110011 
    yyyyyyyy xxxxxxxx 

= U+6FB3 

Si nos fijamos ese código hasta here, verá que es el que usted tenía en su pregunta: 澳.

Para mostrar cómo funciona la decodificación, volví a mis archivos para encontrar mi código de manejo UTF8.Tuve que modificarlo un poco para convertirlo en un programa completo y la codificación se eliminó (ya que la pregunta era sobre decodificación), así que espero no haber introducido ningún error del corte y pegado:

#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; 
} 

se puede ejecutar con su secuencia de bytes (necesitará 4 a fin de utilizar 0 para rellenar fuera) de la siguiente manera:

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

Y si comienza con "10", es la continuación de un carácter de múltiples bytes. –

3

en esencia, si empieza con un 0, que es un poco 7 punto de código Si comienza con 10, es una continuación de un punto de código de varios bytes. De lo contrario, la cantidad de 1 le indica cuántos bytes está codificado este punto de código.

El primer byte indica cuántos bytes codifican el punto de código.

0xxxxxxx 7 bits de punto de código codificadas en 1 bytes

110xxxxx 10xxxxxx 10 bits de punto de código codificados en 2 bytes

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

2

UTF-8 está construido de tal manera que no hay ambigüedad posible sobre la que un personaje se inicia y la cantidad de bytes que tiene.

Es realmente simple.

  • Un byte en el rango 0x80 a 0xBF es Nunca el primer byte de un carácter.
  • Cualquier otro byte es siempre el primer byte de un carácter.

UTF-8 tiene una gran cantidad de redundancia.

Si desea saber cuántos bytes de longitud tiene un carácter, hay varias formas de saberlo.

  • El primer byte siempre te dice cuántos bytes de longitud el personaje es:
    • Si el primer byte es 0x00 a 0x7F, es un byte.
    • 0xC2 a 0xDF significa que son dos bytes.
    • 0xE0 a 0xEF significa que tiene tres bytes.
    • 0xF0 a 0xF4 significa que es de cuatro bytes.
  • O bien, puede simplemente contar el número de bytes consecutivos en el rango 0x80 a 0xBF, porque todos estos bytes pertenecen al mismo carácter que el byte anterior.

Algunos bytes nunca se utilizan, como 0xC1 a 0xC2 o 0xF5 a 0xFF, por lo que si encuentra estos bytes en cualquier lugar, entonces no está mirando a UTF-8.

0

¿por qué hay tantas respuestas complicadas?

3 bytes para 1 carácter chino. utilizando esta función (en 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 
} 
Cuestiones relacionadas