“Fix” Codificación de cadenas en Java

Tengo un String creado a partir de una matriz byte[] , usando la encoding UTF-8.
Sin embargo, debería haberse creado utilizando otra encoding (Windows-1252).

¿Hay una manera de convertir esta cadena de nuevo a la encoding correcta?

Sé que es fácil de hacer si tienes acceso a la matriz de bytes original, pero en mi caso es demasiado tarde porque está dada por una biblioteca de código cerrado.

Como parece haber cierta confusión sobre si esto es posible o no, creo que tendré que proporcionar un amplio ejemplo.

La pregunta afirma que la entrada (inicial) es un byte[] que contiene datos codificados en Windows-1252 . Llamaré a ese byte[] ib (para “bytes iniciales”).

Para este ejemplo elegiré la palabra alemana “Bär” (que significa oso) como entrada:

 byte[] ib = new byte[] { (byte) 0x42, (byte) 0xE4, (byte) 0x72 }; String correctString = new String(ib, "Windows-1252"); assert correctString.charAt(1) == '\u00E4'; //verify that the character was correctly decoded. 

(Si su JVM no es compatible con esa encoding, entonces puede usar ISO-8859-1 en su lugar, porque esas tres letras (y la mayoría de las demás) están en la misma posición en esas dos codificaciones).

La pregunta continúa para indicar que algún otro código (que está fuera de nuestra influencia) ya convirtió ese byte[] en una cadena usando la encoding UTF-8 (llamaré a esa String para “cadena de entrada”). Esa String es la única entrada que está disponible para lograr nuestro objective (si estuviera disponible, sería trivial):

 String is = new String(ib, "UTF-8"); System.out.println(is); 

Esto obviamente produce la salida incorrecta “B “.

El objective sería producir ib (o la deencoding correcta de ese byte[] ) solo is disponible.

Ahora, algunas personas afirman que obtener los bytes codificados en UTF-8 a partir de eso devolverá una matriz con los mismos valores que la matriz inicial:

 byte[] utf8Again = is.getBytes("UTF-8"); 

Pero eso devuelve la encoding UTF-8 de los dos caracteres B y y definitivamente devuelve el resultado incorrecto cuando se vuelve a interpretar como Windows-1252:

 System.out.println(new String(utf8Again, "Windows-1252"); 

Esta línea produce la salida “B”, que es totalmente incorrecta (también es el mismo resultado que sería el resultado si la matriz inicial contenía la palabra “Bür”).

Entonces, en este caso, no puede deshacer la operación, porque la información se pierde.

De hecho, hay casos en que tales codificaciones erróneas se pueden deshacer. Es más probable que funcione, cuando todas las secuencias de bytes posibles (o al menos existentes) son válidas en esa encoding. Como UTF-8 tiene varias secuencias de bytes que simplemente no son valores válidos, tendrá problemas.

Intenté esto y funcionó por alguna razón

Código para reparar el problema de encoding (no funciona perfectamente, lo que veremos en breve):

  final Charset fromCharset = Charset.forName("windows-1252"); final Charset toCharset = Charset.forName("UTF-8"); String fixed = new String(input.getBytes(fromCharset), toCharset); System.out.println(input); System.out.println(fixed); 

Los resultados son:

  input: …Und ich beweg mich (aber heut nur langsam) fixed: …Und ich beweg mich (aber heut nur langsam) 

Aquí hay otro ejemplo:

  input: Waun da wuan ned wa (feat. Wolfgang Kühn) fixed: Waun da wuan ned wa (feat. Wolfgang Kühn) 

Esto es lo que está sucediendo y por qué el truco anterior parece funcionar:

  1. El archivo original era un archivo de texto codificado UTF-8 (delimitado por comas)
  2. Ese archivo se importó con Excel PERO el usuario ingresó erróneamente Windows 1252 para la encoding (que probablemente fue la encoding predeterminada en su computadora)
  3. El usuario pensó que la importación fue exitosa porque todos los caracteres en el rango ASCII se veían bien.

Ahora, cuando intentamos “revertir” el proceso, esto es lo que sucede:

  // we start with this garbage, two characters we don't want! String input = "ü"; final Charset cp1252 = Charset.forName("windows-1252"); final Charset utf8 = Charset.forName("UTF-8"); // lets convert it to bytes in windows-1252: // this gives you 2 bytes: c3 bc // "Ã" ==> c3 // "¼" ==> bc bytes[] windows1252Bytes = input.getBytes(cp1252); // but in utf-8, c3 bc is "ü" String fixed = new String(windows1252Bytes, utf8); System.out.println(input); System.out.println(fixed); 

El código de corrección de encoding anterior funciona pero falla para los siguientes caracteres:

(Suponiendo que los únicos caracteres utilizan caracteres de 1 byte de Windows 1252):

 char utf-8 bytes | string decoded as cp1252 --> as cp1252 bytes ” e2 80 9d | † e2 80 3f Á c3 81 | à  c3 3f Í c3 8d | à  c3 3f Ï c3 8f | à  c3 3f Ð c3 90 | à  c3 3f Ý c3 9d | à  c3 3f 

Funciona para algunos de los personajes, por ejemplo, estos:

 Þ c3 9e | Þ c3 9e Þ ß c3 9f | ß c3 9f ß à c3 a0 | à c3 a0 à á c3 a1 | á c3 a1 á â c3 a2 | â c3 a2 â ã c3 a3 | ã c3 a3 ã ä c3 a4 | ä c3 a4 ä å c3 a5 | Ã¥ c3 a5 å æ c3 a6 | æ c3 a6 æ ç c3 a7 | ç c3 a7 ç 

NOTA – Originalmente pensé que esto era relevante para su pregunta (y ya que estaba trabajando en lo mismo yo pensé que compartiría lo que aprendí), pero parece que mi problema fue ligeramente diferente. Tal vez esto ayudará a alguien más.

Lo que quieres hacer es imposible. Una vez que tenga una cadena Java, la información sobre la matriz de bytes se pierde. Puede tener suerte haciendo una “conversión manual”. Cree una lista de todos los personajes de Windows-1252 y su asignación a UTF-8. Luego itere sobre todos los caracteres en la cadena para convertirlos a la encoding correcta.

Edición: Como comentó un comentarista, esto no funcionará. Cuando convierte una matriz de bytes de Windows-1252 como si fuera UTF-8, está obligado a obtener excepciones de encoding. (Ver aquí y aquí ).

Puedes usar este tutorial

El conjunto de caracteres que necesita debe definirse en rt.jar (de acuerdo con esto )