Java中如何修复字符串编码问题

23

我有一个使用UTF-8编码从byte[]数组创建的String,但实际上应该使用另一种编码(Windows-1252)创建。

是否有方法将此字符串转换回正确的编码?

如果你拥有原始的字节数组,那么做起来很容易,但在我的情况下,这是不可能的,因为它是由一个封闭源代码库提供的。

4个回答

31

由于似乎有些混淆,关于这个是否可能,我认为需要提供一个详细的例子。

问题声称(初始)输入是包含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[]转换为String(我将称之为“输入字符串”),该String是实现我们目标的唯一输入(如果ib可用,这将是微不足道的):

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

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

目标是只使用is来产生ib(或其正确解码的byte[])。

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

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

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

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

这行代码会输出“B�”,这完全是错误的(如果初始数组包含非单词“Bür”,也将产生相同的输出)。

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

实际上存在一些可以纠正这种错误编码的情况。如果在编码中所有可能的(或至少出现过的)字节序列都是有效的,那么更有可能成功。由于UTF-8有几个字节序列是无效值,所以您会遇到问题。


1
我现在明白问题了。抱歉。这就像是在编码无效的UTF-8字节数组时发生了溢出问题,导致数据丢失。感谢您的教训。 - nicerobot
1
那个“�”是Unicode替换字符,它由三个字节0xEF 0xBF 0xBD组成。 - Thomas Weller

16
我尝试了这个方法,不知何故它起作用了。
修复编码问题的代码(它并不完美,我们很快就会看到):
 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 的单字节字符):
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           ç

注意 - 我最初认为这与您的问题有关(而且因为我自己也在做同样的事情,所以我想分享一下我所学到的东西),但似乎我的问题略有不同。也许这会帮助其他人。


-1

你想做的是不可能的。一旦你有了一个Java字符串,关于字节数组的信息就丢失了。你可以尝试进行“手动转换”。创建一个包含所有windows-1252字符及其映射到UTF-8的列表。然后迭代字符串中的所有字符以将它们转换为正确的编码。

编辑: 正如评论者所说,这种方法行不通。当你将Windows-1252字节数组转换为UTF-8时,你肯定会遇到编码异常。(参见这里这里)。


-3

你可以使用这个教程

你需要的字符集应该在rt.jar中定义(根据这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接