- Java核心技术·卷Ⅱ:高级特性(原书第10版)
- (美)凯S.霍斯特曼
- 1291字
- 2024-12-20 22:35:58
2.2.4 字符编码方式
输入和输出流都是用于字节序列的,但是在许多情况下,我们希望操作的是文本,即字符序列。于是,字符如何编码成字节就成了问题。
Java针对字符使用的是Unicode标准。每个字符或“编码点”都具有一个21位的整数。有多种不同的字符编码方式,也就是说,将这些21位数字包装成字节的方法有多种。
最常见的编码方式是UTF-8,它会将每个Unicode编码点编码为1到4个字节的序列(请参阅表2-1)。UTF-8的好处是传统的包含了英语中用到的所有字符的ASCII字符集中的每个字符都只会占用一个字节。
表2-1 UTF-8编码方式
另一种常见的编码方式是UTF-16,它会将每个Unicode编码点编码为1个或2个16位值(请参阅表2-2)。这是一种在Java字符串中使用的编码方式。实际上,有两种形式的UTF-16,被称为“高位优先”和“低位优先”。考虑一下16位值0x2122。在高位优先格式中,高位字节会先出现:0x21后面跟着0x22。但是在低位优先格式中,是另外一种排列方式:0x22 0x21。为了表示使用的是哪一种格式,文件可以以“字节顺序标记”开头,这个标记为16位数值0xFEFF。读入器可以使用这个值来确定字节顺序,然后丢弃它。
表2-2 UTF-16编码方式
警告:有些程序,包括Microsoft Notepad(微软记事本)在内,都在UTF-8编码的文件开头处添加了一个字节顺序标记。很明显,这并不需要,因为在UTF-8中,并不存在字节顺序的问题。但是Unicode标准允许这样做,甚至认为这是一种好的做法,因为这样做可以使编码机制不留疑惑。遗憾的是,Java并没有这么做,有关这个问题的缺陷报告最终是以“will not fix(不做修正)”关闭的。对你来说,最好的做法是将输入中发现的所有先导的\uFEFF都剥离掉。
除了UTF编码方式,还有一些编码方式,它们各自都覆盖了适用于特定用户人群的字符范围。例如,ISO 8859-1是一种单字节编码,它包含了西欧各种语言中用到的带有重音符号的字符,而Shift-JIS是一种用于日文字符的可变长编码。大量的这些编码方式至今仍在被广泛使用。
不存在任何可靠的方式可以自动地探测出字节流中所使用的字符编码方式。某些API方法让我们使用“默认字符集”,即计算机的操作系统首选的字符编码方式。这种字符编码方式与我们的字节源中所使用的编码方式相同吗?字节源中的字节可能来自世界上的其他国家或地区,因此,你应该总是明确指定编码方式。例如,在编写网页时,应该检查Content-Type头信息。
注意:平台使用的编码方式可以由静态方法Charset.defaultCharset返回。静态方法Charset.availableCharsets会返回所有可用的Charset实例,返回结果是一个从字符集的规范名称到Charset对象的映射表。
警告:Oracle的Java实现有一个用于覆盖平台默认值的系统属性file.encoding。但是它并非官方支持的属性,并且Java库的Oracle实现的所有部分并非都以一致的方式处理该属性,因此,你不应该设置它。
StandardCharsets类具有类型为Charset的静态变量,用于表示每种Java虚拟机都必须支持的字符编码方式:
为了获得另一种编码方式的Charset,可以使用静态的forName方法:
在读入或写出文本时,应该使用Charset对象。例如,我们可以像下面这样将一个字节数组转换为字符串:
提示:有些方法允许我们用一个Charset对象或字符串来指定字符编码方式。由于选择的是StandardCharsets常量,所以无需担心拼写错误。例如,new String(bytes,"UTF 8")就不可接受,并且会引发运行时错误。
警告:在不指定任何编码方式时,有些方法(例如String(byte[])构造器)会使用默认的平台编码方式,而其他方法(例如Files.readAllLines)会使用UTF-8。