1. Prefacio
De hecho, desde que comencé a escribir el código Java, me he encontrado con innumerables problemas confusos y transcodificadores, como el código confuso que ocurre cuando se lee de un archivo de texto en una cadena, el código confuso que ocurre al obtener parámetros de solicitud HTTP en un servlet, código confundido que ocurre cuando se considera que JDBC, etc. estos problemas son muy comunes. Cuando los encuentres, puede resolverlos con éxito buscando en busca de ellos, para que no tenga una comprensión profunda.
Hasta hace dos días, mi compañero de clase me habló sobre un problema de codificación de archivo fuente de Java (este problema se analiza en el último ejemplo), y comenzó con este problema y comenzó con una serie de problemas. Luego discutimos mientras buscamos la información. Era tarde en la noche que finalmente encontramos una pista clave en un blog, resolviendo todas las dudas, y las oraciones que no habíamos entendido antes podían explicarse claramente. Por lo tanto, decidí usar este ensayo para registrar mi comprensión de algunos problemas de codificación y los resultados del experimento.
Algunos de los siguientes conceptos son mi propia comprensión basada en condiciones reales. Si hay algún error, asegúrese de corregirlos.
2. Resumen del concepto
En los primeros días, Internet aún no se había desarrollado, y las computadoras solo se usaban para procesar algunos datos locales, por lo que muchos países y regiones diseñaron esquemas de codificación para idiomas locales. Este tipo de codificación relacionada con la región se llama colectivamente la codificación ANSI (porque son extensiones a los códigos ANSI-ASCII). Sin embargo, no discutieron de antemano cómo ser compatibles entre sí, sino que hicieron la suya, lo que sentó la raíz de los conflictos de codificación. Por ejemplo, la codificación GB2312 utilizada en el continente conflictos con la codificación Big5 utilizada en Taiwán. Los mismos dos bytes representan diferentes caracteres en los dos esquemas de codificación. Con el aumento de Internet, un documento a menudo contiene múltiples idiomas, y la computadora encuentra problemas al mostrarlo porque no sabe a qué codificación pertenecen estos dos bytes.
Tales problemas son comunes en el mundo, por lo que los requieren redefinir un conjunto de caracteres común y la numeración unificada de todos los personajes del mundo está aumentando.
Como resultado, el código Unicode surgió, numeró uniformemente a todos los personajes del mundo. Dado que puede identificar de manera única un carácter, la fuente solo necesita ser diseñada para el código Unicode. Sin embargo, el estándar Unicode define un conjunto de caracteres, pero no especifica el esquema de codificación, es decir, solo define los números abstractos y los caracteres correspondientes, pero no especifica cómo almacenar una cadena de números Unicode. El requisito real es cómo almacenar UTF-8, UTF-16, UTF-32 y otras soluciones. Por lo tanto, las codificaciones con comienzos de UTF pueden convertirse directamente a través de cálculos y valores de unicode (puntos de código, puntos de código). Como su nombre indica, UTF-8 es una codificación de longitud de 8 bits, que es una codificación de longitud variable, que usa 1 a 6 bytes para codificar un carácter (porque está limitado por el rango de Unicode, en realidad es solo 4 bytes como máximo); UTF-16 es una codificación de unidad básica de 16 bits, que también es una codificación de longitud variable, ya sea 2 bytes o 4 bytes; UTF-32 es una longitud fija, y 4 bytes fijos almacenan un número unicode.
En realidad, siempre he sido un poco malentendido sobre Unicode antes. En mi impresión, el código Unicode solo puede alcanzar 0xffff, lo que significa que solo puede representar hasta 2^16 caracteres. Después de leer cuidadosamente Wikipedia, me di cuenta de que el esquema de codificación UCS-2 temprano era realmente así. UCS-2 usó fijamente dos bytes para codificar un carácter, por lo que solo puede codificar caracteres dentro del rango de BMP (plano multilingüe básico, es decir, 0x0000-0xffff, que contiene los caracteres más utilizados del mundo). Para codificar caracteres con unicode mayor que 0xffff, las personas han ampliado la codificación UCS-2 y creado la codificación UTF-16, que es la longitud variable. En el rango BMP, UTF-16 es exactamente lo mismo que UCS-2, mientras que UTF-16 fuera de BMP usa 4 bytes para almacenar.
Para facilitar la descripción a continuación, permítanme explicar el concepto de unidad de código (CodeUnit). El componente básico de una determinada codificación se llama la unidad de código. Por ejemplo, la unidad de código de UTF-8 es 1 byte, y la unidad de código de UTF-16 es de 2 bytes. Es difícil de explicar, pero es fácil de entender.
Para ser compatible con varios idiomas y una mejor plataforma cruzada, Javastring guarda el código Unicode para los caracteres. Solía usar el esquema de codificación UCS-2 para almacenar unicode. Más tarde, descubrió que los caracteres en el rango BMP no eran suficientes, pero para el consumo de memoria y las consideraciones de compatibilidad, no se elevó a UCS-4 (es decir, UTF-32, codificación fija de 4 bytes), sino que adoptó el UTF-16 mencionado anteriormente. El tipo de char puede considerarse como su unidad de código. Esta práctica causa algunos problemas. Si todos los personajes están dentro del rango BMP, está bien. Si hay caracteres fuera de BMP, ya no es una unidad de código correspondiente a un carácter. El método de longitud devuelve el número de unidades de código, no el número de caracteres. El método Charat devuelve naturalmente una unidad de código en lugar de un carácter, que se vuelve problemático al atravesar. Aunque se proporcionan algunos nuevos métodos de operación, todavía es inconveniente y no se puede acceder al azar.
Además, descubrí que Java no procesa literales unicode más grandes que 0xffff al compilar, por lo que si no puede escribir un carácter que no sea BMP, pero conoce su código unicode, debe usar un método relativamente estúpido para dejar que String lo almacene: calcule manualmente el número UTF-16 (cuatro bytes) del carácter, y usar los dos primeros bytes y los últimos dos bytes como un número de UNICODES AN UNICODE, y luego el valor de la cadena). El código de muestra es el siguiente.
public static void main (string [] args) {// string str = ""; // Queremos asignar dicho carácter, suponiendo que mi método de entrada no se pueda escribir //, pero sé que su unicode es 0x1d11e // string str = "/u1d11e"; // Esto no se reconocerá // por lo que se puede calcular a través de la codificación UTF-16 D834 dd1estring str = "/UD834/UDD1E"; // luego escriba System.out.println (str); // Salida con éxito ""}El bloc de notas que viene con Windows se puede guardar como codificación Unicode, que en realidad se refiere a la codificación UTF-16. Como se mencionó anteriormente, las codificaciones de caracteres principales utilizadas están dentro del rango BMP, y dentro del rango BMP, el valor de codificación UTF-16 de cada carácter es igual al valor de Unicode correspondiente, por lo que Microsoft lo llama Unicode. Por ejemplo, ingresé a los dos caracteres "buenos" en el bloc de notas, y luego lo guardé como una codificación de Unicode Big Endian (prioridad de alto bits), y abrí el archivo con Winhex. El contenido es como se muestra en la figura a continuación. Los dos primeros bytes del archivo se llaman Marca de pedido de byte (marca de pedido de bytes), (Fe ff) marca el orden de endian como prioridad de bits altos, y luego (59 7d) es el código unicode "bueno", y (00 61) es el código unicode "A".
Con el código Unicode, el problema no se puede resolver de inmediato, porque en primer lugar, hay una gran cantidad de datos de codificación estándar no unicode en el mundo, y es imposible para nosotros descartarlos. En segundo lugar, la codificación de Unicode a menudo ocupa más espacio que la codificación de ANSI, por lo que desde la perspectiva de ahorrar recursos, la codificación ANSI sigue siendo necesaria. Por lo tanto, es necesario establecer un mecanismo de conversión para que la codificación de ANSI pueda convertirse en unicode para el procesamiento unificado, o Unicode puede convertirse en codificación ANSI para cumplir con los requisitos de la plataforma.
El método de conversión es relativamente fácil de decir. Para la serie UTF o ISO-8859-1, las codificaciones compatibles se pueden convertir directamente a través de los valores de cálculo y unicode (de hecho, también puede ser una búsqueda en la tabla). Para la codificación ANSI sobrante del sistema, solo se puede hacer mirando la mesa. Microsoft llama a esta Tabla de mapeo Codepage (página de código) y clasifica y está numerada por codificación. Por ejemplo, nuestro CP936 común es la página del código GBK, y CP65001 es la página del código UTF-8. La siguiente figura es la tabla de mapeo GBK-> Unicode que se encuentra en el sitio web oficial de Microsoft (visualmente incompleto). Del mismo modo, debe haber una tabla de mapeo unicode-> GBK inversa.
Con una página de código, puede realizar fácilmente varias conversiones de codificación. Por ejemplo, al convertir de GBK a UTF-8, solo necesita dividir los datos por caracteres de acuerdo con las reglas de codificación de GBK, usar los datos codificados de cada carácter para verificar la página del código GBK, obtener su valor de unicode y luego usar el Unicode para verificar la página del código UTF-8 (o calcular directamente), y puede obtener el encendido UTF-8 correspondiente. Lo mismo ocurre con el revés. Nota: UTF-8 es una implementación estándar de Unicode. Su página de código contiene todos los valores de Unicode, por lo que cualquier codificación se convierte a UTF-8 y luego se convertirá hacia atrás no se perderá. En este punto, podemos sacar una conclusión de que para completar el trabajo de conversión de codificación, lo más importante es convertir con éxito a Unicode, por lo que elegir correctamente el conjunto de caracteres (página de código) es la clave.
Después de comprender la naturaleza del problema de pérdida de transcodificación, de repente entendí por qué el marco JSP usó ISO-8859-1 para decodificar los parámetros de solicitud HTTP, lo que llevó al hecho de que tuvimos que escribir tales declaraciones cuando obtuvimos parámetros chinos:
Stringparam=newString(s.getBytes("iso-8859-1"),"UTF-8");
Debido a que el marco JSP recibe una secuencia de bytes binaria codificada por el parámetro, no sabe qué codificación es (o no le importa), y no sabe qué página de código verificar para convertir a Unicode. Luego eligió una solución que nunca causará pérdida. Se supone que estos son los datos codificados por ISO-8859-1, y luego busca en la página del código ISO-8859-1 para obtener la secuencia de Unicode. Debido a que ISO-8859-1 está codificado por bytes, y a diferencia de ASCII, codifica todo el espacio 0 ~ 255, por lo que cualquier byte se puede encontrar en su página de código. Si se pasa de Unicode a la transmisión de bytes original, no habrá pérdidas. De esta manera, para los programadores europeos y estadounidenses que no consideran otros idiomas, pueden decodificar directamente la cadena con el marco JSP. Si quieren ser compatibles con otros idiomas, solo necesitan volver a la transmisión de bytes original y decodificarlo con la página de código real.
He terminado de explicar los conceptos relacionados de unicode y codificación de personajes. A continuación, usaré ejemplos de Java para experimentarlo.
Iii. Análisis de ejemplo
1. Convertir al constructor de cuerda unicode
El método de construcción de la cadena es convertir varios datos codificados en una secuencia Unicode (almacenada en la codificación UTF-16). El siguiente código de prueba se utiliza para mostrar la aplicación del método de construcción de Javastring. Los caracteres no BMP están involucrados en los ejemplos, por lo que no se utilizan los métodos de CodePointat.
public class Test {public static void main(String[] args) throws IOException {// "Hello" GBK encoded data byte[] gbkData = {(byte)0xc4, (byte)0xe3, (byte)0xba, (byte)0xc3};// "Hello" BIG5 encoded data byte[] big5Data = {(byte)0xa7, (byte) 0x41, (byte) 0xa6, (byte) 0x6e}; // construir cadena y decodificarla a unicodeString strfromgbk = new String (gbkdata, "gbk"); string strfrombig5 = new string (big5Data, "big5"); // emergir secuencias de unicode respectivamente (strfromgBk); showUnicode (strfrombig5);} public static void showUnicode (string str) {for (int i = 0; i <str.length (); i ++) {system.out.printf ("// u%x", (int) str.charat (i));} system.println ();}}}Los resultados de la operación son los siguientes
¡Se puede encontrar que, dado que String Masters Unicode Code, debe convertirse a otras codificaciones SOEASY!
3. Uso de Unicode como un puente para realizar la codificación de la conversión mutua
Con la base de las dos partes anteriores, es muy simple realizar la codificación y la conversión mutua. Solo necesitas usarlos juntos. Primero, Newstring convierte los datos codificados originales en una secuencia Unicode, y luego llame a GetBytes para transferir a la codificación especificada.
Por ejemplo, un código de conversión GBK a Big5 muy simple es el siguiente
public static void main (string [] args) lanza UnsupportedEnCodingException {// Suponga que estos son los datos leídos del archivo en una secuencia de byte (gbk codificando) byte [] gbkdata = {(byte) 0xc4, (byte) 0xe3, (byte) 0xBa, (byte) 0xc3}; // Convertir a unicodring a unicodring a unicoder String (gbkdata, "gbk"); // Convertir de unicode a big5 codificando byte [] big5data = tmp.getBytes ("big5"); // segundas operaciones ...}4. Problema de pérdida de codificación
Como se explicó anteriormente, la razón por la cual el marco JSP usa el personaje ISO-8859-1 conjunto para decodificarlo. Primero use un ejemplo para simular este proceso de restauración, el código es el siguiente
Public Class Test {public static void main (string [] args) lanza UnsupportedEncodingException {// JSP Framework recibe 6 bytes de datos byte [] data = {(byte) 0xe4, (byte) 0xbd, (byte) 0xa0, (byte) 0xe5, (byte) 0xa5, (byte) 0xBd}; showbytes (datos); // JSP Framework supone que es ISO-8859-1 codificación, genera una cadena de objeto de cadena tmp = new String (datos, "ISO-8859-1"); // ******************************** // Después de que el desarrollador se imprimió, se imprimió y se encontró que era 6 caracteres europeos, en lugar de los personajes europeos ". Resultado de decodificación: " + tmp); // Entonces, primero obtenga los 6 bytes originales de datos (busque reversamente la página de código de ISO-8859-1) byte [] utfdata = tmp.getBytes (" ISO-8859-1 "); // Impresión de los datos restaurados showBytes (utfdata); // el desarrollador sabe que es utf-8c-8c-codeed, que usa el código de los datos restaurados (usa el código de los datos restaurados (UTFDATA); // el desarrollador sabe que es utf-8c-8c-codeed. UTF-8 para reconstruir el resultado del objeto de cadena string result = new String (UTFData, "UTF-8"); // Imprima nuevamente, ¡es correcto! System.out.println ("UTF-8 Resultado de decodificación:" + resultado);} public static void showbytes (byte [] data) {for (byte b: data) system.out.printf ("0x%x", b); system.println ();}}}}}}}}}}}El resultado de ejecución es el siguiente. La primera salida es incorrecta porque las reglas de decodificación son incorrectas. También verifiqué la página de código incorrectamente y obtuve el Unicode incorrecto. Luego descubrí que los datos se pueden restaurar perfectamente a través de la verificación posterior de Unicode incorrecta de la página del código ISO-8859-1.
Este no es el punto. Si la clave es reemplazar "China" con "China", la compilación será exitosa y el resultado de la operación es como se muestra en la figura a continuación. Además, se puede encontrar además que cuando el número de caracteres chinos es impar, la compilación falla y cuando el número es par, pasa. ¿Por qué es esto? Analicemos en detalle a continuación.
Debido a que Javastring usa unicode internamente, el compilador transcodará nuestros literales de cadena durante la compilación y se convertirá de la codificación del archivo fuente a Unicode (Wikipedia dice que utiliza una codificación ligeramente diferente de UTF-8). Al compilar, no especificamos el parámetro de codificación, por lo que el compilador lo decodificará en GBK por defecto. Si tiene algún conocimiento de UTF-8 y GBK, debe saber que, en general, un personaje chino necesita 3 bytes para usar la codificación UTF-8, mientras que GBK solo necesita 2 bytes. Esto puede explicar por qué la paridad del número de caracteres afectará el resultado, porque si hay 2 caracteres, la codificación UTF-8 ocupa 6 bytes, y la decodificación en GBK puede decodificarse a 3 caracteres. Si es 1 carácter, habrá un byte impapable, que es el lugar donde el signo de interrogación en la figura.
Para ser más específicos, la codificación UTF-8 de la palabra "China" en el archivo fuente es E4B8ADE59BBD. El compilador lo decodifica en GBK. Los 3 pares de bytes buscan CP936 para obtener 3 valores unicode, que son 6D93E15E6D57 respectivamente, correspondientes a los tres caracteres extraños en el gráfico de resultados. Como se muestra en la figura a continuación, después de la compilación, estos tres unicodios en realidad se almacenan en la codificación similar a UTF-8 en el archivo .class. Cuando se ejecuta, el Unicode se almacena en el JVM. Sin embargo, cuando se sale la salida final, aún se codificará y pasará al terminal. La codificación acordada esta vez es la codificación establecida por el área del sistema, por lo que si la configuración de codificación del terminal se cambia, aún se volverá a confiar. Nuestro E15e aquí no define los caracteres correspondientes en el estándar Unicode, por lo que la pantalla será diferente bajo diferentes fuentes en diferentes plataformas.
Se puede imaginar que si el archivo fuente se almacena en la codificación de GBK, y luego engaña al compilador para que diga que es UTF-8, básicamente no se puede compilar y pasar sin importar cuántos caracteres chinos se ingresan, porque la codificación de UTF-8 es muy regular, y los bytes combinados al azar no cumplirán con las reglas de codificación UTF-8.
Por supuesto, la forma más directa de permitir que el compilador convierta la codificación a Unicode correctamente es decir honestamente al compilador cuál es la codificación del archivo fuente.
4. Resumen
Después de esta colección y experimento, aprendí muchos conceptos relacionados con la codificación y me familiaricé con el proceso específico de conversión de codificación. Estas ideas pueden generalizarse a varios lenguajes de programación, y los principios de implementación son similares. Así que creo que ya no ignoraré este tipo de problema en el futuro.
Lo anterior es todo el contenido de este artículo sobre ejemplos de conceptos de codificación como ANSI, Unicode, BMP, UTF, etc. Espero que sea útil para todos. Los amigos interesados pueden continuar referiéndose a otros temas relacionados en este sitio. Si hay alguna deficiencia, deje un mensaje para señalarlo. ¡Gracias amigos por su apoyo para este sitio!