“修复”Java 中的字符串编码

2022-09-02 00:57:48

我有一个从数组创建的,使用UTF-8编码。
但是,它应该是使用其他编码(Windows-1252)创建的。Stringbyte[]

有没有办法将此字符串转换回正确的编码?

我知道如果你可以访问原始字节数组,这很容易做到,但是我的情况为时已晚,因为它是由闭源库提供的。


答案 1

由于似乎对这是否可能存在一些混淆,我认为我需要提供一个广泛的示例。

该问题声称(初始)输入是包含Windows-1252编码数据的输入。我将调用它(对于“初始字节”)。byte[]byte[]ib

对于此示例,我将选择德语单词“Bär”(意思是熊)作为输入:

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.

(如果您的JVM不支持该编码,那么您可以使用ISO-8859-1,因为这三个字母(以及大多数其他字母)在这两种编码中处于同一位置)。

问题继续指出,其他一些代码(不受我们的影响)已经使用UTF-8编码将其转换为字符串(我将称之为“输入字符串”)。这是实现我们目标的唯一可用输入(如果可用,那将是微不足道的):byte[]StringisStringib

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

这显然会产生不正确的输出“B”。

目标是仅生成(或正确解码)可用。ibbyte[]is

现在有些人声称从中获取 UTF-8 编码字节将返回一个与初始数组具有相同值的数组:

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

但这会返回两个字符的 UTF-8 编码,并且在重新解释为 Windows-1252 时肯定会返回错误的结果:B

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

这条线产生输出“Bï¿1/2”,这是完全错误的(如果初始数组包含非单词“Bür”,结果也是相同的输出)。

因此,在这种情况下,您无法撤消该操作,因为某些信息已丢失。

事实上,在某些情况下,这种错误编码是可以撤消的。当所有可能(或至少发生)的字节序列在该编码中都有效时,它更有可能起作用。由于 UTF-8 有几个字节序列根本不是有效值,因此您将遇到问题。


答案 2

我试过这个,它出于某种原因起作用了

修复编码问题的代码(它不能完美地工作,我们很快就会看到):

 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);

结果是:

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

下面是另一个示例:

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

以下是正在发生的事情以及为什么上面的技巧似乎有效:

  1. 原始文件是 UTF-8 编码的文本文件(逗号分隔)
  2. 该文件是使用Excel导入的,但用户错误地输入了Windows 1252进行编码(这可能是他或她计算机上的默认编码)
  3. 用户认为导入成功,因为 ASCII 范围中的所有字符看起来都正常。

现在,当我们尝试“逆转”该过程时,会发生什么:

 // 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);

上述编码修复代码可以正常工作,但对于以下字符,此方法失败:

(假设只有字符使用了 Windows 1252 中的 1 字节字符):

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

它确实适用于某些字符,例如:

Þ       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           ç

注意 - 我最初认为这与您的问题有关(当我自己做同样的事情时,我想我会分享我所学到的东西),但似乎我的问题略有不同。也许这会帮助别人。


推荐