知识点:
在使用String的String(byte[])是,一定要指定编码。否则,他会使用平台默认的缺省编码,这样非常容易引起混乱。
问题:
下面的程序会打印社么?
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到255,解决方法使用new String(bytes, 'ISO-8859-1')
// 实际结果:不同的平台,打印的结果有所查询。
结果是不是出乎大家的意料呢?的确如此。
产生这个结果的原因:
首先,byte 数组用从0到 255每一个可能的 byte 数值进行了初始化,然后这些 byte 数值通过String构造器被转换成了 char 数值。最后,char 数值被转型为 int 数值并被打印。打印出来的数值肯定是非负整数,因为 char 数值是无符号 的,因此,你可能期望该程序将按顺序打印出0 到 255的整数。 如果你运行该程序,可能会看到这样的序列。但是在运行一次,可能看到的就不 是这个序列了。我们在四台机器上运行它,会看到四个不同的序列,包括前面描 述的那个序列。这个程序甚至都不能保证会正常终止,比打印其他任何特定字符 串都要缺乏这种保证。它的行为完全是不确定的。 这里的罪魁祸首就是String(byte[])构造器。有关它的规范描述道:“在通过 解码使用平台缺省字符集的指定 byte 数组来构造一个新的String时,该新 String的长度是字符集的一个函数,因此,它可能不等于 byte 数组的长度。当 给定的所有字节在缺省字符集中并非全部有效时,这个构造器的行为是不确定 的”[Java-API]。 到底什么是字符集?从技术角度上讲,它是“被编码的字符集合和字符编码模式 的结合物”[Java-API]。换句话说,字符集是一个包,包含了字符、表示字符的 数字编码以及在字符编码序列和字节序列之间来回转换的方式。转换模式在字符 集之间存在着很大的区别:某些是在字符和字节之间做一对一的映射,但是大多 数都不是这样。ISO-8859-1 是唯一能够让该程序按顺序打印从0 到 255的整数 的缺省字符集,它更为大家所熟知的名字是Latin-1[ISO-8859-1]。 |
解决方法:
ISO-8859-1 是唯一能够让该程序按顺序打印从0 到 255的整数的缺省字符集,它更为大家所熟知的名字是Latin-1[ISO-8859-1]。
J2SE运行期环境(JRE)的缺省字符集依赖于底层的操作系统和语言。如果你想 知道你的 JRE的缺省字符集,并且你使用的是5.0 或更新的版本,那么你可以通 过调用java.nio.charset.Charset.defaultCharset()来了解。如果你使用的是 较早的版本,那么你可以通过阅读系统属性“file.encoding”来了解。 幸运的是,你没有被强制要求必须去容忍各种稀奇古怪的缺省字符集。当你在 char 序列和 byte 序列之间做转换时,你可以且通常是应该显式地指定字符集。 除了接受byte 数字之外,还可以接受一个字符集名称的String构造器就是专为 此目的而设计的。如果你用下面的构造器去替换在最初的程序中的String构造 器,那么不管缺省的字符集是什么,该程序都保证能够按照顺序打印从0 到 255 的整数: |
总结:
这个谜题的教训是:每当你要将一个 byte 序列转换成一个String时,你都在使 用某一个字符集,不管你是否显式地指定了它。如果你想让你的程序的行为是可 预知的,那么就请你在每次使用字符集时都明确地指定。对 API的设计者来说, 提供这么一个依赖于缺省字符集的String(byte[])构造器可能并非是一个好主 意。 |