将字符串从一个字符集转换为另一个字符集

Converting String from One Charset to Another

我正在努力将字符串从一种字符集转换为另一种字符集,并阅读了许多关于它的示例,最后找到了下面的代码,这对我来说很不错,作为字符集编码的新手,我想知道,如果它是正确的方法。

public static byte[] transcodeField(byte[] source, Charset from, Charset to) {
    return new String(source, from).getBytes(to);
} 

要将字符串从 ASCII 转换为 EBCDIC,我必须这样做:

System.out.println(new String(transcodeField(ebytes,
                Charset.forName("US-ASCII"), Charset.forName("Cp1047"))));

要从 EBCDIC 转换为 ASCII,我必须这样做:

System.out.println(new String(transcodeField(ebytes,
                Charset.forName("Cp1047"), Charset.forName("US-ASCII"))));

您找到的代码 (transcodeField) 不会将 String 从一种编码转换为另一种编码,因为 String 没有编码¹。它将字节从一种编码转换为另一种编码。该方法仅在您的用例满足 2 个条件时才有用:

  1. 您的输入数据是一种编码的字节
  2. 您的输出数据需要是另一种编码的字节

既然如此,那就直截了当了:

byte[] out = transcodeField(inbytes, Charset.forName(inEnc), Charset.forName(outEnc));

如果输入数据包含无法在输出编码中表示的字符(例如将复杂的 UTF8 转换为 ASCII),这些字符将替换为 ? replacement symbol,数据会被破坏。

然而 lot of people ask "", to which a lot of people 答案包含以下片段:

String s = new String(source.getBytes(inputEncoding), outputEncoding);

这完全是牛****。 getBytes(String encoding) 方法 returns 一个字节数组,其中包含以指定编码编码的字符(如果可能,再次将无效字符转换为 ?)。带有第二个参数的 String 构造函数从字节数组创建一个新的 String,其中字节采用指定的编码。现在,由于您刚刚使用 source.getBytes(inputEncoding) 来获取这些字节,因此它们 而不是 outputEncoding 中编码(除非编码使用相同的值,这对于“正常”字符,如 abcd,但与重音字符 éêäöñ) 等更复杂的字符不同。

那么这是什么意思呢?这意味着当你有一个 Java String 时,一切都很棒。 Strings 是 unicode,这意味着您的所有字符都是安全的。当您需要将 String 转换为字节时,问题就来了,这意味着您需要决定一种编码。选择一个 unicode 兼容的编码,如 UTF8UTF16 等是很好的。这意味着即使您的字符串包含各种奇怪的字符,您的字符仍然是安全的。如果您选择不同的编码(US-ASCII 最不支持),您的字符串必须仅包含该编码支持的字符,否则会导致字节损坏。

现在终于有一些好的和坏的用法的例子了。

String myString = "Feng shui in chinese is 風水";
byte[] bytes1 = myString.getBytes("UTF-8");  // Bytes correct
byte[] bytes2 = myString.getBytes("US-ASCII"); // Last 2 characters are now corrupted (converted to question marks)

String nordic = "Här är några merkkejä";
byte[] bytes3 = nordic.getBytes("UTF-8");  // Bytes correct, "weird" chars take 2 bytes each
byte[] bytes4 = nordic.getBytes("ISO-8859-1"); // Bytes correct, "weird" chars take 1 byte each
String broken = new String(nordic.getBytes("UTF-8"), "ISO-8859-1"); // Contains now "Här är några merkkejä"

最后一个例子表明,即使两种编码都支持北欧字符,但它们使用不同的字节来表示它们并且在解码时使用错误的编码导致Mojibake。因此,不存在“将字符串从一种编码转换为另一种编码”这样的事情,您永远不应该使用损坏的示例。

另请注意,您应该始终指定使用的编码(getBytes()new String()),因为您不能相信默认编码总是您想要的编码。

作为最后一期,Charset and Encoding 不是一回事,但它们非常相关。

¹ 从技术上讲,字符串在 JVM 内部存储的方式是 UTF-16 编码,最多 Java 8,variable encoding 从 Java 9 开始,但是开发人员不需要关心这些。


注意

可能有一个损坏的字符串,并且能够通过摆弄编码来恢复它,这可能是这种“将字符串转换为其他编码”误解的来源。

// Input comes from network/file/other place and we have misconfigured the encoding 
String input = "Här är några merkkejä"; // UTF-8 bytes, interpreted wrongly as ISO-8859-1 compatible
byte[] bytes = input.getBytes("ISO-8859-1"); // Get each char as single byte
String asUtf8 = new String(bytes, "UTF-8"); // Recreate String as UTF-8

如果 input 中没有任何字符损坏,该字符串现在将被“修复”。然而,正确的做法是在读取 input 时使用正确的编码,而不是事后修复它。特别是如果它有可能被损坏。