`

《中文化和国际化问题(2)--Java国际化基础》

阅读更多

Java的字符类型

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



 

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

 

编码(encoding

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

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



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



 

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

  • 英文可转换成ISO-8859-1
  • 中文可转换成GB2312GBKBIG5或是GB18030等。
  • 日文可以转换成SJISISO-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编码的方式保存到一个文本文件中。

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class TestEncoding {
    public static void main(String[] args) {
        try {
            writeStringToFile("我爱Alibaba", "c:/ilovealibaba.txt", "GBK");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void writeStringToFile(String str, String filename,
            String charset) throws IOException {
        FileOutputStream ostream = new FileOutputStream(filename);
        OutputStreamWriter writer = new OutputStreamWriter(ostream, charset);
        try {
            writer.write(str);
        } finally {
            writer.close();
        }
    }
}




 

 

件,事实上可以使用任何输出流,例如使用ByteArrayOutputStream将可将字节流保存在内存中。

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

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class TestDecoding {
    public static void main(String[] args) {
        try {
            System.out
                    .println(readStringFromFile("c:/ilovealibaba.txt", "GBK"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String readStringFromFile(String filename, String charset)
            throws IOException {
        FileInputStream istream = new FileInputStream(filename);
        InputStreamReader reader = new InputStreamReader(istream, charset);
        StringBuffer string = new StringBuffer();
        char[] buffer = new char[128];
        int count = 0;
        try {
            while ((count = reader.read(buffer)) != -1) {
                string.append(buffer, 0, count);
            }
        } finally {
            reader.close();
        }
        return string.toString();
    }
}

 


 
 

  

 获得字节,然后用同样的方法转换成字符。例如,通过ByteArrayInputStream,可以从内存中的byte[]数组中取得字节流。

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

import java.io.UnsupportedEncodingException;

public class TestStringGetBytes {
    public static void main(String[] args) {
        try {
            dumpBytes("我爱Alibaba", "GBK");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    public static void dumpBytes(String str, String charset)
            throws UnsupportedEncodingException {
        byte[] bytes = str.getBytes(charset);

        // 显示bytes的内容,每行显示4个
        for (int i = 0; i < bytes.length; i++) {
            System.out.print(Integer.toHexString(bytes[i] & 0xFF));
            System.out.print(" ");

            if ((i + 1) % 4 == 0) {
                System.out.println();
            }
        }

        System.out.println();
    }
}

运行的结果为:

ce d2 b0 ae
41 6c 69 62
61 62 61
下面的程序,使用String(bytes, charset)构造函数,也实现了读取一个文件的内容,并以指定编码方式(GBK)解码成字符串的功能。

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class TestNewString {
    public static void main(String[] args) {
        try {
            System.out.println(
                    readStringFromFile("c:/ilovealibaba.txt", "GBK"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String readStringFromFile(String filename, String charset) throws IOException {
        FileInputStream istream = new FileInputStream(filename);
        ByteArrayOutputStream ostream = new ByteArrayOutputStream();

        byte[] buffer = new byte[128];
        int count = 0;
        try {
            while ((count = istream.read(buffer)) != -1) {
                ostream.write(buffer, 0, count);
            }
        } finally {
            istream.close();
        }

        byte[] stringBytes = ostream.toByteArray();

        // 使用指定charset,将bytes[]转换成字符串
        return new String(stringBytes, charset);
    }
}

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

其它和国际化相关的功能
java.util.ResourceBundle

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

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

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

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

public class TestDateFormat {
    public static void main(String[] args) {
        Date date = new Date();

        System.out.println(DateFormat.getDateInstance(DateFormat.FULL, Locale.CHINA).format(date));
        System.out.println(DateFormat.getDateInstance(DateFormat.FULL, Locale.US).format(date));
    }
}

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

1.   NumberFormat

2.   DecimalFormat

3.   DateFormat

4.   SimpleDateFormat

5.   MessageFormat

6.   ChoiceFormat

检测字符属性

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

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

char ch;
...
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
    // ch是一个字母
}
...
if (ch >= '0' && ch <= '9') {
    // ch是一个数字
}
...
if ((ch == ' ') || (ch =='\r')  || (ch =='\n') || (ch == '\t')) {
    // ch是一个空白
}

这样的程序没有考虑除了英文和其它少数几种语言之外的语言习惯。例如:西腊字母“αβγ”也应该算是字母,汉字中全角数字“123”也是数字,全角空格“ ”(U+3000)也属于空白。正确的程序应该是这样的:

char ch;
...
if (Character.isLetter(ch)) {
...
if (Character.isDigit(ch)) {
...
if (Character.isSpaceChar(ch)) {
...

下面列出了Character中用来判定字符属性的方法:

1.   Character.isDigit

2.   Character.isLetter

3.   Character.isLetterOrDigit

4.   Character.isLowerCase

5.   Character.isUpperCase

6.   Character.isSpaceChar

7.   Character.isDefined

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

char ch;
...
Character.UnicodeBlock block = Character.UnicodeBlock.of(ch);

if (block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) {
    // 是CJK统一汉字
}

更多Character类细节请参阅Java API文档。

字符串比较和排序

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

检测字符串的边界

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

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

 

 

 

 

 

 

  • 大小: 12.6 KB
  • 大小: 18 KB
  • 大小: 17.6 KB
  • 大小: 25.8 KB
  • 大小: 16.7 KB
  • 大小: 20.9 KB
  • 大小: 20.7 KB
  • 大小: 33.4 KB
  • 大小: 24 KB
  • 大小: 24.5 KB
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    java 实现国际化 中英文语言切换

    1. **Java 国际化基础** Java 提供了 `java.util.Locale` 类来表示不同的语言环境,如英文(`en`)和中文(`zh`)。`Locale` 包含语言、国家和变种等信息,例如 `Locale.US` 表示美国英语,`Locale.CHINA` 表示简体...

    Java 程序国际化教程+源码

    Java程序的国际化(i18n)是为了使软件能够适应不同地区的语言和文化习惯,它涉及到日期、时间、数字格式、货币符号、排序规则、文本方向等多方面的处理。本教程将详细介绍Java如何实现这一功能,并提供源码供学习者...

    java web 国际化

    在Java Web开发中,Locale对象是国际化程序的基础。Locale对象代表了一个特定的地理、政治或文化地区。通过Locale对象,程序能够根据用户的位置信息加载相应的资源文件。在Web应用程序中,可以通过...

    java 国际化转换

    ### Java国际化转换详解 #### 一、Java国际化概念与意义 在软件开发过程中,随着产品的全球化,越来越多的应用程序需要支持多种语言环境。这就引出了一个重要的概念——国际化(Internationalization)。通常简称...

    javamelody-javamelody-core-1.67.0.zip

    7. **国际化支持**:JavaMelody支持多种语言,包括中文,使得非英语环境的用户也能轻松使用。 8. **扩展性**:JavaMelody设计为模块化,允许开发者根据需求选择监控的组件,或者添加自定义的监控点。 9. **安全性...

    java国际化i8n

    Java 国际化(i18n)是一个关键特性,允许开发者创建能够适应不同地区、语言和文化的软件应用。在Java中,i18n主要通过使用资源包(Resource Bundle)来实现,这是一种存储特定区域信息的方式。下面将详细介绍如何在...

    JAVA_API1.6文档(中文)

    java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。 java.util.concurrent 在并发编程中很常用的实用工具类...

    java中文API --------

    11. **国际化**:`java.text`和`java.util.Locale`支持多语言环境,方便程序在不同地区运行。 12. **并发工具**:`java.util.concurrent`包提供了高级并发工具,如`Semaphore`、`CountDownLatch`和`CyclicBarrier`...

    Java Internationalization国际化教程

    Java 国际化(i18n)是Java平台提供的一种强大的功能,使得软件能够适应全球不同地区的语言和文化环境。在Java中,国际化的实现主要依赖于`java.text`和`java.util`包中的类,如`ResourceBundle`、`DateFormat`、`...

    全中文Java-Jdk-1.8api参考手册.zip

    13. **国际化(i18n)**:`java.text`和`java.util.Locale`支持不同地区和语言的文本处理。 14. **XML处理**:`javax.xml`包提供了处理XML文档的工具,如解析、转换和验证。 这份全中文的JDK 1.8 API参考手册不仅...

    JAVA-API-1.6中文文档.zip

    7. **国际化与本地化**:`java.text`和`java.util.Locale`类支持多语言环境,方便开发全球化应用。 8. **事件处理**:`java.awt`和`javax.swing`包提供了图形用户界面(GUI)组件和事件处理机制,如按钮、文本框、...

    JAVA-API-1.8中文文档(完整高清)

    文档中的内容不仅限于这些特性,还包含了基础类型、IO流、网络编程、反射、异常处理、集合框架、多线程、国际化等广泛的Java知识。每个类、接口和方法都有详细的解释,包括其作用、参数、返回值、示例代码等,便于...

    Java 2D API 中文使用指

    Java 2D 的设计采用了层次化的结构,分为以下几个层次: - **最高层**:面向开发者的图形绘制接口,提供易于使用的 API。 - **中间层**:包含图形管道和渲染器,用于将图形数据转化为最终的像素输出。 - **最低层**...

    基于struts2-hibernate-spring的Java Web系统国际化设计与实现.zip

    Spring框架作为Java企业级应用的核心,它提供依赖注入(DI)和面向切面编程(AOP),并整合了其他框架如Struts2和Hibernate。Spring的Internationalization(i18n)支持允许开发者定义资源包,包含不同语言的字符串...

    java-1.6中文API.

    8. **国际化与本地化**:`java.text`和`java.util.Locale`类支持多语言环境,方便程序适应不同地区的需求。 9. **日期和时间**:`java.util.Date`和`java.util.Calendar`类处理日期和时间,而`java.text....

    jdk-1.6中文版+Java-ee中英文对照chm开发文档

    这个版本引入了许多新特性和改进,包括增强的编译器、性能优化、新的API以及对安全管理、并发编程、国际化和网络服务的支持。其中,最引人注目的是"invokestatic"指令的优化,使得静态方法调用速度显著提升。此外,...

    java中文API文档

    以上只是Java API文档中的一部分关键知识点,实际上,Java API包含的内容远不止这些,涵盖了诸如XML处理、国际化、日期时间、数学运算、正则表达式、图形用户界面等多个方面。中文版的Java API文档对于中文使用者来...

    java 1.6 中文 帮助文档

    9. **国际化与本地化**:Java 1.6支持多语言环境,通过Locale类和ResourceBundle类可以实现应用程序的国际化和本地化。 10. **Swing组件库**:虽然JavaFX在后续版本中逐渐成为新的UI开发工具,但在1.6中,Swing仍然...

Global site tag (gtag.js) - Google Analytics