Puzzle 18: String Cheese

字符串的奶酪

虽然Java是跨平台的,但Java编码却有字符集的问题,这就是之一:

代码片段:

public class StringCheese {

public static void main(String[] args) {

byte[] bytes = new byte[256];

for (int i = 0; i < 256; i++)

bytes[i] = (byte) i;

String str = new String(bytes);

for (int i = 0, n = str.length(); i < n; i++)

System.out.println((int) str.charAt(i) + " ");

}

}

在不同编码集的平台下运行, 得到的结果在这里。有的输出结果并非 0~225 这些数字,好生奇怪阿,把上面黑体加粗部分的代码改成如下就好了。

String str = new String(bytes, "ISO-8859-1");

以下分析原因。

按照 Java API 文档, 内容如下:

public String(byte[] bytes)

Constructs a new String by decoding the specified array of bytes using the platform's default charset. The length of the new String is a function of the charset, and hence may not be equal to the length of the byte array.

The behavior of this constructor when the given bytes are not valid in the default charset is unspecified. The CharsetDecoder class should be used when more control over the decoding process is required.

Parameters:

bytes - The bytes to be decoded into characters

中文:

public String(byte[] bytes)

构造一个新的 String,方法是使用平台的默认字符集解码字节的指定数组。新的 String 的长度是一个字符集函数,因此不能等于字节数组的长度。

当给定字节在给定字符集中无效的情况下,该构造方法无指定的行为。当需要进一步控制解码过程时,应使用 CharsetDecoder 类。

参数:

bytes - 要解码为字符的字节

上面从API文档中摘录的内容, 着重标出的部分已经能说明问题了. 也就是

String(byte[] bytes) 等价于 String(byte[] bytes, java.nio.charset.Charset.defaultCharset().displayName())

而 java.nio.charset.Charset.defaultCharset().displayName() 是依赖于具体平台的, 所以这个上面的程序得不到一个确定的结果.

为什么加上参数 "ISO-8859-1", 即把编码指定为 "ISO-8859-1" 就正常了? 指定别的编码行吗?

答: "ISO-8859-1" 即 Latin 1 字符集, 是最基础的字符集, 扩展自 ASCII 的单字节编码. 能够将 byte[] 转成字符串时, 一个 byte 对应一个字符, 还原的时候不出错. 而 UTF-8 这种变长字节的编码, 在byte[] 转成字符串时, 可能几个 byte 转成了一个字符, 故而输出不到预期结果.

问题TODO:

按理 ISO-8859-X 系列的编码都能达到 ISO-8859-1 的效果, 但事实上不是这样, 只有用 ISO-8859-1 时能产生正常输出. 这是什么原因?

参考

  1. 本站关联文档: 计算机字符编码问题 Java 代码的诸编码
  2. API文档参考:java.lang.String(bytes[])

Google Code: 代码参考