`

中文化和国际化问题权威解析之二:Java国际化基础

 
阅读更多

我们知道 Unicode 为国际化( I18n )提供了坚实的基础。但是 Unicode 不等同于国际化。使用 Unicode Java 语言,若是使用不当,同样达不到国际化的目的。让我们来看一下 Java 是怎样处理 Unicode 的。

Java 的字符类型

C 语言不同, Java 的字符类型 char 是一个 16 位长的整数,而 C 语言的 char 8 位,等同于一个字节,只能表示单字节的字符(拉丁语系文字)。所以 Java 可以直接用一个 char 来表示一个 Unicode 字符(包括中文、英文、日文 …… ),大大简化了字符和字符串的操作。

因为 Java 字符总是 Unicode 字符,所以在后文中,如果不加说明, 字符 char 都是指 16 位的 Unicode 字符,而 字节 byte 都是指 8 位字节。

编码( encoding

然而,当今多数计算机系统,都是以字节为存储运算的基本单元。这就使得在 Java 中,用 Unicode 表示的字符串无法直接写到文件中或保存到数据库中。必须以某一种方式,将字符串转换成便于传输和存储的字节流才行。这种将 Unicode 字符转换成字节的操作,就叫做 字符编码 encoding )。

前面说过 Unicode 有两种字节表示法: UTF-8 UTF-16 。所以将 Unicode UTF-8 UTF-16 编码是最直接和自然的事了。以上面的 我爱 Alibaba あいう 为例,用 Big-endian (高位字节在前,低位字节在后)的 UTF-16 编码,可以表示成:

我们也可以把同样的字符串转换成 UTF-8 UTF-8 是变长的编码,对于 ASCII 码字符,不需要改变,就已经是 UTF-8 了,但一个中文要用三个字节来表示:

使用 UTF-16 UTF-8 编码的数据,必须使用支持 Unicode 的软件来处理,例如支持 Unicode 的文本编辑器。目前存在的大量软件,不一定都支持 Unicode 。因此我们往往将 Unicode 转换成某一种本地字符集,例如:

  • 英文可转换成 ISO-8859-1
  • 中文可转换成 GB2312 GBK BIG5 或是 GB18030 等。
  • 日文可以转换成 SJIS ISO-2022-JP 等。
  • 韩文可以转换成 ISO-2022-KR 等。

本地字符集名目之多,无法全部列举。最重要是,大多数字符集只映射到 Unicode 中的部分字符,且字符集之间互相交错,互不兼容。

那么,如果在将 Unicode 转换到某一本地字符集时,发现这一编码字符集不包含这个字符,怎么办呢?例如: 我爱 Alibaba” 这个字符串(简体中文),如果转换成繁体中文的 BIG5 编码,就会变成: ?Alibaba” 。原来, Unicode 规定,转换时碰到 看不懂 的字符,一律用 “? 0x3F 表示。

这就解释了一种常见的 乱码 情形:好端端的页面,显示在浏览器上却变成了无数个问号。原因就是 Java 在输出网页时,使用了错误的编码方式。后面将更详细地解释这个问题。

解码( decoding

同样的,如果我们要从文件或数据库中读取文本数据,因为我们读到的是一个字节流,所以我们需要使用正确的编码方法,将字节流恢复成字符流。这个操作叫做 解码 decoding )。

如果指定了错误的编码方法,那么就会得到不正确的字符流。和编码过程类似, Unicode 规定,在解码时,发现 看不懂 的字节,一律用 0xFFFD 表示。例如:将 我爱 Alibaba” UTF-8 的编码方式保存在一个文件中,用繁体中文编码 BIG5 读入,就会变成: ����� libaba” 。因为 UTF-8 字节序列 E6 88 91 E7 88 不是一个合法的 BIG5 编码序列,而第六个字节 B1 和后面一个字节 41 (原本是字母 “A” )碰巧可以构成一个 BIG5 字符

反过来说,是不是经过解码的字符序列中,不包含问号 “?” ,就代表解码方法是正确的呢?显然不是!

最典型的错误就是:用 ISO-8859-1 来解码中文文件。这导致了更隐蔽的错误。因为 ISO-8859-1 的字符编码正好和 Unicode 的最前面 256 个字符相同,换句话说,在 ISO-8859-1 编码之前加上 “00” 就变成了 Unicode 。正是由于这个特殊性, ISO-8859-1 似乎成了 万能 的编码而被广泛地误用!

仍以 我爱 Alibaba” 为例,如果用 ISO-8859-1 解码此文件,我们可以得到一个看似 合法 的字符串:

很明显,使用 ISO-8859-1 解码中文文件的人,只是把 Unicode 字符看作是 16 位的 字节 而已。对 Java 而言, 我爱 这两个字符不代表中文字符 我爱 ,只不过是 4 个欧洲字符和符号而已。

Java 对国际化的支持

Java I/O

Java 中,主要是通过输入输出流来进行编码和解码的。输入输出流分成两种:

字节流( Octet Stream

java.io.InputStream java.io.OutputStream 派生的,负责读写字节( byte )。 例如: java.io.FileInputStream java.io.ByteArrayInputStream java.io.FileOutputStream java.io.ByteArrayOutputStream 等。

字符流( Character Stream

java.io.Reader java.io.Writer 派生的,负责读写字符( char )。例如: java.io.StringReader java.io.StringWriter 等。

而联系这两种流的,分别是 OutputStreamWriter InputStreamReader 。由这两个类来实现 Java 字符的编码和解码。

下面的完整的例子演示了 Java 如何把一个包含中文的字符串,以 GBK 编码的方式保存到一个文本文件中。

  1. import java.io.FileOutputStream;
  2. import java.io.IOException;
  3. import java.io.OutputStreamWriter;
  4. public class TestEncoding{
  5. public static void main(String[]args){
  6. try {
  7. writeStringToFile( "我爱Alibaba" , "c:/ilovealibaba.txt" , "GBK" );
  8. } catch (IOExceptione){
  9. e.printStackTrace();
  10. }
  11. }
  12. public static void writeStringToFile(Stringstr,Stringfilename,
  13. Stringcharset) throws IOException{
  14. FileOutputStreamostream= new FileOutputStream(filename);
  15. OutputStreamWriterwriter= new OutputStreamWriter(ostream,charset);
  16. try {
  17. writer.write(str);
  18. } finally {
  19. writer.close();
  20. }
  21. }
  22. }

当然,除了输出到文件,事实上可以使用任何输出流,例如使用

ByteArrayOutputStream 将可将字节流保存在内存中。

下面的完整的例子演示了 Java 如何读取一个文件,并把文件的内容以 GBK 方式解码。

  1. import java.io.FileInputStream;
  2. import java.io.IOException;
  3. import java.io.InputStreamReader;
  4. public class TestDecoding{
  5. public static void main(String[]args){
  6. try {
  7. System.out
  8. .println(readStringFromFile( "c:/ilovealibaba.txt" , "GBK" ));
  9. } catch (IOExceptione){
  10. e.printStackTrace();
  11. }
  12. }
  13. public static StringreadStringFromFile(Stringfilename,Stringcharset)
  14. throws IOException{
  15. FileInputStreamistream= new FileInputStream(filename);
  16. InputStreamReaderreader= new InputStreamReader(istream,charset);
  17. StringBufferstring= new StringBuffer();
  18. char []buffer= new char [ 128 ];
  19. int count= 0 ;
  20. try {
  21. while ((count=reader.read(buffer))!=- 1 ){
  22. string.append(buffer, 0 ,count);
  23. }
  24. } finally {
  25. reader.close();
  26. }
  27. return string.toString();
  28. }
  29. }

当然也可以从任何输入流中获得字节,然后用同样的方法转换成字符。例如,通过

ByteArrayInputStream ,可以从内存中的 byte[] 数组中取得字节流。

字符串处理

另一种常见的编码和解码的方法,是通过 Java String 类完成的。下面的程序演示了 Java 如何使用 String.getBytes() 方法,将字符串编码成指定形式的字节的。

  1. import java.io.UnsupportedEncodingException;
  2. public class TestStringGetBytes{
  3. public static void main(String[]args){
  4. try {
  5. dumpBytes( "我爱Alibaba" , "GBK" );
  6. } catch (UnsupportedEncodingExceptione){
  7. e.printStackTrace();
  8. }
  9. }
  10. public static void dumpBytes(Stringstr,Stringcharset)
  11. throws UnsupportedEncodingException{
  12. byte []bytes=str.getBytes(charset);
  13. //显示bytes的内容,每行显示4个
  14. for ( int i= 0 ;i<bytes.length;i++){
  15. System.out.print(Integer.toHexString(bytes[i]& 0xFF ));
  16. System.out.print( "" );
  17. if ((i+ 1 )% 4 == 0 ){
  18. System.out.println();
  19. }
  20. }
  21. System.out.println();
  22. }
  23. }

运行的结果为:

  1. ced2b0ae
  2. 41 6c 69 62
  3. 61 62 61

下面的程序,使用

String(bytes, charset) 构造函数,也实现了读取一个文件的内容,并以指定编码方式( GBK )解码成字符串的功能。
  1. import java.io.ByteArrayOutputStream;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. public class TestNewString{
  5. public static void main(String[]args){
  6. try {
  7. System.out.println(
  8. readStringFromFile( "c:/ilovealibaba.txt" , "GBK" ));
  9. } catch (IOExceptione){
  10. e.printStackTrace();
  11. }
  12. }
  13. public static StringreadStringFromFile(Stringfilename,Stringcharset) throws IOException{
  14. FileInputStreamistream= new FileInputStream(filename);
  15. ByteArrayOutputStreamostream= new ByteArrayOutputStream();
  16. byte []buffer= new byte [ 128 ];
  17. int count= 0 ;
  18. try {
  19. while ((count=istream.read(buffer))!=- 1 ){
  20. ostream.write(buffer, 0 ,count);
  21. }
  22. } finally {
  23. istream.close();
  24. }
  25. byte []stringBytes=ostream.toByteArray();
  26. //使用指定charset,将bytes[]转换成字符串
  27. return new String(stringBytes,charset);
  28. }
  29. }

注意:

上面这段程序只是演示 String(bytes, charset) 构造函数,如果要读取大量的文本,这种方式的性能肯定不如前面使用 InputStreamReader 的程序示例。

其它和国际化相关的功能

java.util.ResourceBundle

通过 ResourceBundle ,我们可以把特定语言相关的信息放在程序之外。这样当我们要在已有产品的基础上,增加一种语言或地区的支持时,只需要增加一种 ResourceBundle 的实现即可。

数字、货币、日期、时间的格式化

中国人表示日期的习惯是: “2003 5 24 星期六 ,而美国人则习惯于: “Saturday, May 24, 2003” Java 程序代码可以不关心这些差别。在运行时刻, Java 可以根据不同的语言或地区习惯,自动按不同的格式风格显示这些内容。

  1. import java.text.DateFormat;
  2. import java.util.Date;
  3. import java.util.Locale;
  4. public class TestDateFormat{
  5. public static void main(String[]args){
  6. Datedate= new Date();
  7. System.out.println(DateFormat.getDateInstance(DateFormat.FULL,Locale.CHINA).format(date));
  8. System.out.println(DateFormat.getDateInstance(DateFormat.FULL,Locale.US).format(date));
  9. }
  10. }

除了

DateFormat java.text 包中还包括了很多其它格式化类。

1. NumberFormat

2. DecimalFormat

3. DateFormat

4. SimpleDateFormat

5. MessageFormat

6. ChoiceFormat

检测字符属性

前文提到, Unicode 不仅定义了统一的字符集,而且为这些字符及编码数据提出应用的方法以及对语义数据进行补充。而 Java 可以直接查看 Unicode 所定义的这些字符属性。

传统的非国际化的程序常常这样检测一个字符是否属于字母、数字还是空白:

  1. char ch;
  2. ...
  3. if ((ch>= 'a' &&ch<= 'z' )||(ch>= 'A' &&ch<= 'Z' )){
  4. //ch是一个字母
  5. }
  6. ...
  7. if (ch>= '0' &&ch<= '9' ){
  8. //ch是一个数字
  9. }
  10. ...
  11. if ((ch== '' )||(ch== '/r' )||(ch== '/n' )||(ch== '/t' )){
  12. //ch是一个空白
  13. }

这样的程序没有考虑除了英文和其它少数几种语言之外的语言习惯。例如:西腊字母

“αβγ” 也应该算是字母,汉字中全角数字 123 也是数字,全角空格   U+3000 )也属于空白。正确的程序应该是这样的:
  1. char ch;
  2. ...
  3. if (Character.isLetter(ch)){
  4. ...
  5. if (Character.isDigit(ch)){
  6. ...
  7. if (Character.isSpaceChar(ch)){
  8. ...

下面列出了

Character 中用来判定字符属性的方法:

1. Character.isDigit

2. Character.isLetter

3. Character.isLetterOrDigit

4. Character.isLowerCase

5. Character.isUpperCase

6. Character.isSpaceChar

7. Character.isDefined

此外, Unicode 还为每个统一字符定义了很多属性。我们可以通过 Character 相应方法取得这些属性。例如可以用下面的代码判定一个字符是否为 中日韩统一汉字

  1. char ch;
  2. ...
  3. Character.UnicodeBlockblock=Character.UnicodeBlock.of(ch);
  4. if (block==Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS){
  5. //是CJK统一汉字
  6. }

更多

Character 类细节请参阅 Java API文档

字符串比较和排序

字符间的逻辑顺序不一定和 Unicode 编码的数值顺序一致。利用 java.text.Collator 可以比较两个 Unicode 字符串的逻辑顺序。

检测字符串的边界

在应用中,我们经常需要检测字符串的边界:检测字符( character )、词( word )、句子( sentence )、行( line )的边界。例如,显示一段文字,需要在屏幕的右边界处对文本断行。断行不是任意的。例如,你不能把一个英文单词拆开。

使用 java.text.BreakIterator 可以实现字符串边界的检测。

以上只是简单地列举了 Java 中和国际化相关的功能。具体描述这些内容,超出了本文的议题。可以从 Java 文档中取得更详细的信息: Java国际化指南

分享到:
评论

相关推荐

    Java核心技术:卷Ⅱ高级特性(原书第8版)高清中文

    理解反射有助于实现元编程,例如动态代理、序列化和配置文件的解析。 5. **安全性**:Java的安全模型提供了一套强大的机制来保护代码和数据。本书将介绍如何创建安全的Java应用,包括权限管理、沙箱模型以及证书和...

    java api中文文档

    - JAXB:用于XML和Java对象之间的绑定,方便XML的序列化和反序列化。 10. **Java虚拟机(JVM)**: - 类加载机制:加载、验证、准备、解析和初始化五个阶段。 - 内存模型:堆、栈、方法区、本地方法栈、程序...

    java1.6中文帮助文档

    - **国际化和本地化**: - **字符编码**:支持更多的字符集和编码方式,如UTF-8。 - **本地化支持**:增强了日期时间、数字格式化等功能。 #### 3. Java 1.6 的安装与配置 - **安装步骤**: - 下载官方提供的...

    JAVA核心技术 第9版【中文版】

    4. **基本类型与数据结构**:详细解析整型、浮点型、字符型、布尔型等基本类型,以及数组的创建、初始化和操作。 5. **控制流**:涵盖条件语句(if、switch)、循环(for、while、do-while)、跳转语句(break、...

    java 指南中英文对照

    《Java指南》是一本深入解析Java编程语言的重要参考资料,它由SUN公司(后被甲骨文公司Oracle收购)官方发布,旨在为开发者提供全面、权威的Java学习资源。中文版则是通过BING翻译引擎将英文原版进行翻译,虽然机器...

    美河提供.Java数据结构和算法中文第二版

    《美河提供.Java数据结构和算法中文第二版》是一本深度解析Java环境下数据结构与算法原理及应用的专业书籍。此书由国外计算机科学专家Robert Lafore编写,经过国内技术团队的精心翻译与修订,旨在为中国程序员和...

    java面试题及技巧4

    │ │ 一些其它网站的java基础精华贴.txt │ │ 新建 文本文档.txt │ │ 经验总结.txt │ │ 资料目录.txt │ │ 题目.txt │ │ │ ├─HTML Pages │ │ │ Desktop_.ini │ │ │ Low Level Security in Java....

    Hadoop权威指南中文版(第二版)+Hadoop in Action

    《Hadoop权威指南中文版(第二版)》与《Hadoop in Action》及《Pro Hadoop》这三本书是深入理解和掌握Hadoop生态系统的关键资源。Hadoop作为一个分布式计算框架,其核心是解决大规模数据处理的问题,它允许在廉价...

    Inside the Java Virtual Machine(中文第2版)_带标签

    《Inside the Java Virtual Machine》(中文第二版)是一本深入探讨Java虚拟机(JVM)的权威著作。这本书详尽地介绍了JVM的工作原理、内存管理、类加载机制以及字节码执行等核心概念,是Java开发者提升技术深度的...

    《thinking in java》第三版中文版.rar

    《Thinking in Java》是Bruce Eckel的经典著作,被誉为学习Java编程的权威指南。第三版针对Java SE 5.0进行了全面更新,包含了Java语言的最新特性。这本书以其深入浅出的讲解方式,全面覆盖了从基础到高级的Java编程...

    Maven权威指南中文完整版清晰

    《Maven权威指南中文完整版清晰》是一本深入解析Maven构建工具的教程,它为Java开发者提供了详尽的指导,帮助他们理解和掌握Maven的使用。Maven是Apache软件基金会开发的一个项目管理工具,主要用于Java项目的构建、...

    Java核心技术,卷2(原书第8版).pdf 中文 自制完整书签

    本书介绍了Java中关于文本和字符集、日期和时间格式、数字和货币格式化、消息和资源束等方面的国际化和本地化技术,帮助开发者创建易于本地化的应用程序。 ### 结论 《Java核心技术,卷2(原书第8版)》不仅是一本...

    HBase权威指南中文版+官方文档

    ### HBase权威指南知识点梳理 #### 一、HBase简介与文档...以上内容为《HBase权威指南》中文版中的核心知识点梳理,涵盖了从HBase的基础概念到高级应用的各个方面,旨在帮助读者全面掌握HBase的相关技术和最佳实践。

    Thinking in Java(中英文版)

    8. **反射与注解**:深入解析Java的反射机制,允许程序在运行时检查类、接口和方法的信息。同时,介绍了注解的使用及其在元编程中的应用。 9. **垃圾收集与内存管理**:讨论了Java的自动内存管理,包括垃圾收集的...

    thinking in java(中文第三版)

    3. **集合框架**:Java的集合框架是其强大的工具之一,书中详细解析了List、Set、Map等各种接口和实现类,如ArrayList、LinkedList、HashSet、HashMap等,以及它们的应用场景和性能比较。 4. **异常处理**:异常...

    HBase权威指南 中文版

    ### HBase权威指南知识点梳理 #### 一、简介与快速开始 ...以上是对《HBase权威指南》中文版文档的主要知识点总结,涵盖了从基础概念、配置部署到高级应用等多个方面,旨在帮助读者全面掌握HBase的相关知识和技术。

    学Java好书介绍

    Horstmann 和 Gary Cornell合作编写,是Java领域非常权威的技术参考书之一。 - **重要知识点**: - 卷一涵盖了Java的基础知识,包括语言基础、类与对象、数组、字符串等。 - 卷二则更深入地探讨了高级主题,如多...

Global site tag (gtag.js) - Google Analytics