2010-01-29 25 views
16

Recientemente me di cuenta de que no entiendo completamente el proceso de codificación de cadenas de Java.De la compilación al tiempo de ejecución, ¿cómo funciona realmente la codificación de Java String?

Considere el siguiente código:

public class Main 
{ 
    public static void main(String[] args) 
    { 
     System.out.println(java.nio.charset.Charset.defaultCharset().name()); 
     System.out.println("ack char: ^"); /* where^= 0x06, the ack char */ 
    } 
} 

Dado que los caracteres de control son interpreted differently between windows-1252 and ISO-8859-1, elegí el carbón ack para la prueba.

Ahora lo compilo con diferentes codificaciones de archivos, UTF-8, windows-1252 y ISO-8859-1. Ambas compilan exactamente lo mismo, byte por byte verificado por md5sum.

Luego ejecutar el programa:

$ java Main | hexdump -C 
00000000 55 54 46 2d 38 0a 61 63 6b 20 63 68 61 72 3a 20 |UTF-8.ack char: | 
00000010 06 0a            |..| 
00000012 

$ java -Dfile.encoding=iso-8859-1 Main | hexdump -C 
00000000 49 53 4f 2d 38 38 35 39 2d 31 0a 61 63 6b 20 63 |ISO-8859-1.ack c| 
00000010 68 61 72 3a 20 06 0a        |har: ..| 
00000017 

$ java -Dfile.encoding=windows-1252 Main | hexdump -C 
00000000 77 69 6e 64 6f 77 73 2d 31 32 35 32 0a 61 63 6b |windows-1252.ack| 
00000010 20 63 68 61 72 3a 20 06 0a      | char: ..| 
00000019 

correctamente da salida a la 0x06 no importa qué codificación se está utilizando.

Ok, sigue produciendo el mismo 0x06, que se interpretaría como el carácter imprimible [ACK] por las páginas de códigos de windows-1252.

Eso me lleva a algunas preguntas:

  1. está siendo compilado espera que la página de códigos/juego de caracteres del archivo de Java para ser idéntico al juego de caracteres por defecto del sistema bajo el cual está siendo compilado? ¿Son los dos siempre sinónimos?
  2. La representación compilada no parece depender del juego de caracteres en tiempo de compilación, ¿es así?
  3. ¿Esto implica que las cadenas dentro de los archivos Java se pueden interpretar de manera diferente en el tiempo de ejecución si no utilizan caracteres estándar para el conjunto de caracteres/configuración regional actual?
  4. ¿Qué más debería saber realmente acerca de la codificación de cadenas y caracteres en Java?
+0

No está claro a qué se refiere con "compilarlo con diferentes codificaciones de archivo". ¿Quiere decir que guarda el archivo en diferentes codificaciones y luego compila cada uno de esos archivos usando el modificador de codificación en javac? Si es así, ¿cómo sabe qué basura aleatoria se está terminando en los archivos fuente después de guardarlos en esas codificaciones? No puede poner un carácter de control literal en su fuente y esperar que sobreviva a la serialización de caracteres codificados. –

+0

Un archivo no es más que una secuencia de bytes. Esos bytes se interpretan de manera diferente dependiendo de la codificación de caracteres en los que se supone que están. Por lo tanto, me refiero a cadenas que contienen 'char's que pueden interpretarse de manera diferente, ya sea en tiempo de ejecución o en tiempo de compilación, asumiendo el archivo fue codificado en diferentes conjuntos de caracteres. –

+0

Para ser explícito sobre el paso de compilación, utilicé la propiedad de codificación sun para establecer el juego de caracteres en tiempo de compilación: 'javac -encoding windows-1252 Main.java', con el conjunto de codificación apropiado. –

Respuesta

19
  1. archivos de origen pueden estar en cualquier codificación
  2. Es necesario indicar al compilador la codificación de archivos de origen (por ejemplo,javac -encoding...); de lo contrario, se asume que codifica la plataforma
  3. En binarios del archivo de clase, los literales de cadena se almacenan como (modificado) UTF-8, pero a menos que usted trabaja con código de bytes, esto no importa (ver JVM spec)
  4. cadenas en Java son UTF -16, siempre (ver Java language spec)
  5. el System.outPrintStream transformará sus cadenas de UTF-16 a bytes en el sistema de codificación antes de escribirlos en la salida estándar

Notas:

3

Si compila con diferentes codificaciones, estas codificaciones solo afectan a sus archivos de origen. Si no tiene ningún carácter especial dentro de sus fuentes, no habrá diferencia en el código de bytes resultante.

Para el tiempo de ejecución, se utiliza el juego de caracteres predeterminado del sistema operativo. Esto es independiente del juego de caracteres que usaste para compilar.

1

Erm basado en this y this el carácter de control ACK es exactamente el mismo en ambas codificaciones. La diferencia en el enlace que señaló es cómo DOS/Windows realmente tiene símbolos para la mayoría de los caracteres de control en Windows-1252 (como los caracteres Heart/Club/Spade/Diamond y simileys), mientras que ISO-8859 no.

+0

Tiene razón, el ack char es 0x06 en ambas codificaciones. Tal vez fallé, pero estaba tratando de encontrar un escenario en el que se interpretara de manera diferente en función del juego de caracteres actual. La publicación de blog de @ McDowell hace un trabajo mucho mejor al demostrar lo que estaba intentando hacer. –

13

Un resumen de "Lo que debe saber" acerca de codificaciones de cadena en Java:

  • Un String ejemplo, en la memoria, es una secuencia de 16 bits " unidades de código ", que Java maneja como char valores. Conceptualmente, esas unidades de código codifican una secuencia de "puntos de código", donde un punto de código es "el número atribuido a un personaje dado según el estándar Unicode". Los puntos de código van de 0 a un poco más de un millón, aunque hasta el momento solo se han definido 100 mil. Los puntos de código de 0 a 65535 están codificados en una única unidad de código, mientras que otros puntos de código utilizan dos unidades de código. Este proceso se llama UTF-16 (también conocido como UCS-2). Hay algunas sutilezas (algunos puntos de código no son válidos, por ejemplo 65535, y hay un rango de 2048 puntos de código en el primer 65536 reservado precisamente para la codificación de los otros puntos de código).
  • Las páginas de códigos y similares no afectan la forma en que Java almacena las cadenas en la RAM. Es por eso que "Unicode" comienza con "Uni". Siempre que no realice E/S con sus cadenas, se encuentra en el mundo de Unicode, donde todos utilizan el mismo mapeo de caracteres para codificar los puntos.
  • Los conjuntos de caracteres entran en acción al codificar series en bytes o decodificar cadenas desde bytes. A menos que se especifique explícitamente, Java utilizará un juego de caracteres predeterminado que depende de la "configuración regional" del usuario, una noción global borrosa de lo que hace que una computadora en Japón hable japonés. Cuando imprime una cadena con System.out.println(), la JVM convertirá la cadena en algo adecuado para dondequiera que vayan esos caracteres, lo que a menudo significa convertirlos a bytes utilizando un juego de caracteres que depende de la configuración regional actual (o lo que la JVM adivinó de la configuración regional actual))
  • Una aplicación Java es el compilador Java. El compilador de Java necesita interpretar los contenidos de los archivos fuente, que son, a nivel de sistema, solo un montón de bytes. El compilador de Java luego selecciona un juego de caracteres predeterminado para eso, y lo hace dependiendo de la configuración regional actual, al igual que lo haría Java, porque el compilador de Java está escrito en Java. El compilador de Java (javac) acepta un indicador de línea de comando (-encoding) que se puede utilizar para anular esa opción predeterminada.
  • El compilador de Java produce archivos de clase que son independientes de la configuración regional. Los literales de cadena terminan en esos archivos de clase con (tipo de) codificación UTF-8, independientemente del juego de caracteres que el compilador de Java usó para interpretar los archivos fuente. La configuración regional del sistema en el que se ejecuta el compilador de Java afecta cómo se interpreta el código fuente, pero una vez que el compilador de Java ha entendido que su cadena contiene el número de punto de código 6, este punto de código será el que llegue a los archivos de clase y ninguno otro Tenga en cuenta que los puntos de código 0 a 127 tienen la misma codificación en UTF-8, CP-1252 e ISO-8859-1, por lo tanto, lo que obtienes no es de extrañar.
  • Aun así, las instancias String no dependen de ningún tipo de codificación, siempre y cuando permanezcan en la RAM, algunas de las operaciones que desea realizar en cadenas dependen de la configuración regional. Esta no es una cuestión de codificación; pero una configuración regional también define un "idioma" y sucede que las nociones de mayúsculas y minúsculas dependen del idioma que se utiliza. El Sospechoso habitual está llamando al "unicode".toUpperCase(): esto produce "UNICODE", excepto si la configuración regional actual es turca, en cuyo caso obtendrá "UNİCODE" (el "I" tiene un punto). La suposición básica aquí es que si la configuración regional actual es turca, entonces los datos que la aplicación está gestionando son probablemente textos turcos; personalmente, encuentro esta suposición en el mejor de los casos cuestionable. Pero así es.

En términos prácticos, debe especificar codificaciones explícitamente en su código, al menos la mayor parte del tiempo. No llame al String.getBytes(), llame al String.getBytes("UTF-8"). El uso de la codificación predeterminada dependiente de la configuración regional está bien cuando se aplica a algunos datos intercambiados con el usuario, como un archivo de configuración o un mensaje para mostrar de inmediato; pero en otro lugar, evite los métodos dependientes de la ubicación siempre que sea posible.

Entre otras partes de Java que dependen de la configuración regional, existen calendarios. Está todo el negocio de la zona horaria, que depende de la "zona horaria", que debe estar relacionada con la posición geográfica de la computadora (y esto no es parte de la "configuración regional" stricto sensu ...). Además, innumerables aplicaciones Java fallan misteriosamente cuando se ejecutan en Bangkok, porque en una localidad tailandesa, Java adopta por defecto el calendario budista según el cual el año actual es 2553.

Como regla general, supongamos que el mundo es vasto (¡lo es!) y mantenga las cosas genéricas (no haga nada que dependa de un juego de caracteres hasta el último momento, cuando realmente se debe realizar la E/S).

Cuestiones relacionadas