`

《中文化和国际化问题(3)--Java中文问题分析》

阅读更多

章从实际的中文问题中,分析问题的根本原因,以及解决之道。

注意,本章虽然着重说明中文问题,但本章所推出的结论却是适合于世界所有语言文字的。

概述

我们在实际开发中碰到的中文问题,真是形形色色,无法一一列举。但是它们不是随机产生的,而是有规律可循,有办法解决的。

我们碰到最多的中文问题,都发生在使用Java ServletWEB应用时。其次,使用Java Mail API发送e-mail也会有类似的问题。从表象上区分,大致上有以下几种:

  1. 好端端的中文显示成了问号“?”,且一个中文变成2个问号。
  2. 好端端的中文显示成了问号“?”,且一个中文变成1个问号。
  3. 好端端的中文显示成了看不懂的符号,如“ÎÒ°®Alibaba”
  4. WEB页面中部分中文显示正常,部分中文是乱码。

要分析这些问题的根本原因,首先要了解这些中文字符的输入源,其次是了解这些字符被输出到用户浏览器经过了哪些转换和输出环节。中文字符可以来源于:

  1. 程序内嵌的中文,我们在程序里直接书写中文字符串。
  2. 文本文件,利用FileInputStream读入文件内容并转换成字符。
  3. XML文件,利用XML解析器读入内存。
  4. 数据库,利用SQL查询,取得的结果。
  5. 模板文件,例如VelocityWebMacro模板,我们使用模板生成WEB页面。
  6. JSP页面,在JSP生成的WEB页面。
  7. 用户通过浏览器提交的表单。

中文字符被装入内存以后,还要经过若干个转换和输出环节,最后才能到达用户的浏览器被用户看到。

  1. 文本通过response.getWriter()输出到浏览器端。
  2. 浏览器读取服务器的HTTP响应,并将响应中包含的HTML页面显示在浏览器上。
  3. 文本可能被写入XML文件、文本文件、数据库中。

以上列举的任何一个环节发生错误,都可能产生乱码现象。因此发生乱码现象时,不要慌,想想这个乱码的文本是从哪里来的,又是以什么方式输出的。

字符的输入、转换、输出环节

内嵌在程序代码中的中文

因为Java源代码(.java)本身是一个文本文件,所以和读普通文本文件一样,编译器(javac)必须以字节流的方式读入文件内容,并以适当的编码转换成Unicode字符而存储在Java字节码文件(.class)中。例如:Java源代码文件中包含GBK编码的中文字符,则使用下面的命令编译:

 

  1. javac -encoding GBK MyClass.java

如果不指定

-encoding参数,javac会使用系统默认的编码:在中文Windows上,默认是GBK,在英文Linux上,默认是ISO-8859-1。因此,如果文件是在英文Linux下编译而未指定-encoding,那么文件中的中文我爱Alibaba”就会变成“ÎÒ°®Alibaba”了。

从文本文件中读入的字符

正如前面的 TestDecoding例子所示,在读入文本文件时,需要指定正确的编码。如果不指明编码,那么Java就会使用系统默认的编码来转换文件中的字节流。下列代码往往会产生问题:

  1. public static String readStringFromFile(String filename) throws IOException {
  2.     // 正确的方法:
  3.     // FileInputStream istream = new FileInputStream(filename);
  4.     // InputStreamReader reader = new InputStreamReader(istream, charset);
  5.     
  6.     // 可能会导致乱码的方法(取决于运行平台的默认编码):
  7.     FileReader reader = new FileReader(filename);
  8.     ...
  9. }
XML文件中读入的字符

XML标准极为严格地遵守Unicode标准。XML文件的字符编码是定义在XML文件中,而不是定义在XML解析器中的。如果不明确指定,任何标准的XML解析总是以UTF-8的方式解码XML文件。可以用下面的方式在XML文件中指定字符编码:

 

  1. <?xml version="1.0" encoding="GB2312"?>

在解码过程中,如果

XML解析器发现一个非法的字节,不会像Java一样,转换成问号“?”,而是立即报错。所以XML解析器一般总会取得正确的Unicode字符。

注意

注意,XML规范并没有定义GBKGB18030编码,因此不能在XML文件使用这两种编码。目前可以使用的中文编码是GB2312BIG5。相信这种情况以后会改变。如果确实想使用中文大字符集,请指定UTF-8作为XML文件的编码。

数据库

首先,数据库一般都可以设置以何种字符编码方式存储文本;其次,数据库的客户端 —— JDBC驱动 —— 必须设置成和数据库的内置字符编码一致;最后,尽可能使用UTF-8存取文本数据,因为这样可以在数据库中方便地存储所有国家的文字。

注意

我们AlibabaOracle数据库目前采用7ASCII码存储文本(包括中文),这是一个极大的错误,已经导致了很多问题。我们后面会讲到。

模板文件

VelocityWebMacro是常用的Java模板系统。模板文件也是简单的文本文件。VelocityWebMacro都可以在各自的配置文件定义读取模板所用的字符编码方式。例如Velocity可以这样设置:

 

 

  1. input.encoding=GBK

如果是在

Turbine(一种基于MVC设计模式的WEB应用框架,http://jakarta.apache.org/turbine)中调用Velocity,可以在Turbine的配置文件中设置:
  1. services.VelocityService.input.encoding=GBK

这样

Velocity就可以用GBK编码读取模板文件。

JSP页面

JSP是一种特殊的WEB页面,在第一次使用时,被自动编译成一个普通的servlet。在JSP的开头指定JSP的字符编码:

 

  1. <%@page contentType="text/html; charset=GBK"%>

上面这行告诉

JSP

  1. javac -encoding GBK命令选项来编译JSP所生成的servlet源代码。

2.            使用GBK编码输出JSP servlet中的所有字符,相当于:

 

  1. response.setContentType("text/html; charset=GBK")

3.           

使用GBK解码用户的表单输入,相当于:

  1. request.setCharacterEncoding("GBK")
用户通过浏览器提交的表单

浏览器是根据页面的content type来决定以何种方式来编码用户输入的表单的。例如,一个页面的content typetext/html; charset=GBK,那么,当用户按下页面中的submit按钮时,浏览器自动将用户的输入用GBK方式编码并发送回服务器端。服务器接到用户的请求后,需要用正确的方式来解码,方法是:

 

  1. request.setCharacterEncoding("GBK");

然后再调用

request.getParameter(parameterName)时就可以得到正确的Unicode字符。

注意

必须在第一次调用request.getParameter(parameterName)之前调用request.setCharacterEncoding(charset),因为解码是在第一次调用request.getParameter(parameterName)时发生的。

Servlet规范规定,如果没有设定request.setCharacterEncoding,则使用ISO-8859-1来解码用户输入的表单,而不是使用系统默认的编码

对于multipart form(例如,上传图片的form表单),情况要复杂一些。因为servlet并没有直接支持multipart form。所以大多数应用程序使用了第三方的工具包来解析multipart form,例如:Oreilly COS工具包。然而,这些工具包大多使用系统默认的编码来解析用户表单,和servlet规范不一致。如果你的servlet代码没有特别指明编码方式,则两种form表单将有不同的表现,必有一种情况会出现乱码现象。

Servlet输出

Servlet可以用两种方式向浏览器输出内容:

  1. 字节流方式 —— 输出到response.getOutputStream()。一般用来输出二进制内容,例如图片。
  2. 字符流方式 —— 输出到response.getWriter()。用来输出文本类型的内容,如HTML和纯文本。

在此我们只讨论输出文本的情形:response.getWriter()。在调用response.getWriter()前,我们必须设置content type

 

  1. response.setContentType("text/html; charset=GBK");

response.getWriter()

 

通过content type中指定的字符编码来决定如何将字符流转换成字节流。

Turbine中,在配置文件中指定下面的内容,Turbine会为你自动设置content type

  1. locale.default.charset=GBK
浏览器如何确定页面的字符编码

浏览器收到从WEB服务器返回的页面时,

  1. 首先检查HTTP响应中指定的content type,也就是servlet通过response.setContentType方法设置的值。如果content type中指定字符编码(例如text/html; charset=GBK),则使用这种方式解码这个页面。

2.            如果HTTP响应中没有指定字符集,那么浏览器会检查HTML页面中是否包含:

 

  1. <meta http-equiv="Content-Type" content="text/html; charset=GBK">

如果找到,则使用这里指定的字符编码。

  1. 如果既没有在HTTP响应中指定字符编码,也没有在HTML内容中指定字符编码,则浏览器根据一定的规则自动确定页面的字符编码。例如,在英文环境中,浏览器会使用ISO-8859-1,简体中文环境中,则使用GBK。用户也可以根据自己的需要手工改变这一设置。
其它输出环节

文本还可能被写入XML文件、文本文件、数据库中。类似的,输出文件时一般都要指定字符编码。如果不指定,通常Java会选择系统默认的编码。这为程序运行的结果产生了不确定因素。

乱码分析

明白了各输入、转换、输出环节是怎样工作的,我们的分析工作就有头绪了。在深入分析之前,有不少情况,观察乱码的表面现象就可以得到大概的结论。

一个中文变成了两个问号“?”

这个现象通常表明字符在输入时出错,也就是解码错误。

 

 

虽然输出编码是对的,但在此之前,由于错误的输入编码,每个中文字变成了两个不相干的欧洲字符。而这些欧洲字符的编码和GBK编码是相冲突的(但也不一定完全冲突,例如上例中的第三个字节B0,被转换成GBKE3A1)。因此大部分中文被输出成两个问号。

如果出现乱码的中文字是从Velocity模板读入的,说明Velocity配置文件中的input.encoding设置不正确;如果这个中文字是从数据库读入的,说明数据库的配置出错,也有可能文本在保存进数据库之前就已经错了;如果这个中文字是从用户表单输入的,很可能是你忘了调用request.setCharacterEncoding("GBK")

一个中文变成了一个问号“?”

这个现象通常表明字符在输出时出错,也就是编码错误。

 

 

这很可能是因为没有设置response.setContentType("text/html; charset=GBK")

中文显示成了看不懂的符号,如“ÎÒ°®Alibaba”

这个现象通常表明字符在输入输出时都出错了。

 

 

明眼人一看就发现,实际上在这种情况下,最后输出到浏览器上的字节流是正确的!只是因为content type被设成了错误的ISO-8859-1编码,所以才导致浏览器显示不正确的。事实上,用户可以手工改变浏览器的设置,使浏览器使用GBK对字节流重新解码。

看起来象是数学中的负负得正。为什么会这样呢?这是因为ISO-8859-1编码的特殊性导致的。ISO-8859-1字符集的编码范围是0000-00FF,正好和一个字节的编码范围相对应。这种特性保证了使用ISO-8859-1进行编码/解码可以保持编码数值不变。虽然中文字符在输入JVM时,被错误地成了两个欧洲字符,但由于输出时也是用ISO-8859-1,结果被开的中文字的两半又被神奇地合并在一起。

这种情形在英文版的Linux上最常发生,事实上我们公司的很多程序就是这样做的。英文版Linux的系统默认编码为ISO-8859-1。假设我们的servlet从模板中生成动态网页:

  • 输入环节 —— 如果我们不指定模板系统的字符编码,那么,Java会使用系统默认的编码(ISO-8859-1)读入模板文件,从而将一个GBK中文编码看作两个欧洲字符。
  • 输出环节 —— 如果我们在设置response.setContentType("text/html")时不指明charset,按servlet规范,系统应以ISO-8859-1输出页面。从而恢复了正确的字节流。
  • 浏览器输入环节 —— 浏览器发现HTTP响应中未指定字符编码,则检查HTML中有没有<meta http-equiv="Content-Type" content="text/html; charset=GBK">之类的标记,如果有,则以GBK显示页面。此时页面显示是完全正常的(没有乱码)。
  • 如果浏览器发现HTTP响应中未指定字符编码,并且HTML中也没有定义meta标记,则使用系统默认的编码。这取决于运行浏览器的平台和浏览器的设置。一般英文平台会以ISO-8859-1显示页面,从而显示成乱码。中文平台有可能可以正确显示页面。

同样的代码,如果在中文Windows上运行,因为系统默认编码为GBK,因而会转变成一个中文变成一个问号的情形。

如果页面中有部分字符不是来源于模板,而是来源于XML文件或UTF-8编码的数据库,又会转变成WEB页面中部分中文显示正常,部分中文是乱码的情形。

此外,把一个中文字符转换成两个欧洲字符,不仅使字符串变长了一倍,影响效率,而且前面所说的和Unicode相关的功能一概失效:断句断词、排序、查看字符属性、格式化日期和数字。

可见,使用这种方法显示中文,引入了诸多不确定因素,实在不是一种可取的方法。但是很多程序员满足于完成任务,却不求甚解,不理解Unicode的精义。甚至网上很多的文章也主张这么做,真是可悲可叹。

WEB页面中部分中文显示正常,部分中文是乱码

很明显,这是由于同一页面中的字符是从不同的输入源取得的。假设有如下常见情形:

  • 从模板取得的中文字使用了系统默认编码,中文Windows上是GBK,英文Linux上是ISO-8859-1,后者将一个中文转变成了两个欧洲字符。
  • XML取得的中文字总是正确的Unicode字符。
  • 从用户表单读入的中文字,如果不指定编码(request.setCharacterEncoding),总是以ISO-8859-1解码。

结果就是:

 

Content Type

浏览器环境

服务器环境

从模板取得的中文字

XML取得的中文字

从用户表单取得的中文字

text/html

中文Windows

中文Windows

一个中文变成1个问号

一个中文变成1个问号

碰巧正常

英文Linux

碰巧正常

一个中文变成1个问号

碰巧正常

英文Windows

中文Windows

一个中文变成1个问号

一个中文变成1个问号

中文变成欧洲字符

英文Linux

中文变成欧洲字符

一个中文变成1个问号

中文变成欧洲字符

text/html; charset=GBK

任意

中文Windows

正常

正常

一个中文变成2个问号

英文Linux

一个中文变成2个问号

正常

一个中文变成2个问号

注意

所谓碰巧正常是指虽然在服务器上,一个中文被当作两个欧洲字符处理,但是输出到浏览器以后,又被重新组合成了正确的字节序列,并且浏览器按默认的选项,会以中文GBK解码此序列。对于中文变成欧洲字符的情形,可以在浏览器上人工设置字符编码为GBK,或是在HTML中设置<meta http-equiv="Content-Type" content="text/html; charset=GBK">标记,来显示出中文。

深入分析

以上只是分析了最常见的乱码现象。实际上,还可能会发生更复杂一点的情形。但是无论什么情形,都可以通过仔细分析中文字符经过的每一个输入转换输出

分享到:
评论
发表评论

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

相关推荐

    深入分析Java中的中文编码问题

    Java中的中文编码问题是开发者在进行国际化或多语言支持应用开发时需要特别注意的一个方面。通过对编码格式的理解、常见问题的分析以及合理的解决方案,可以有效地减少甚至避免中文乱码的问题,提高应用的质量和用户...

    eclipse-java-2024-03-R-win32-x86-64.zip

    9. **国际化支持**:Eclipse支持多种语言,包括中文,为全球开发者提供了良好的用户体验。 10. **持续集成**:与Hudson或Jenkins等持续集成工具的集成,可以自动化构建和测试过程,提高软件开发效率。 "eclipse-...

    java国际化实现框架底层源码

    Java国际化实现框架底层源码分析 Java国际化(i18n,Internationalization)是为了支持不同地区的语言和文化差异,提供了一种灵活的方式来管理和显示应用程序的文本、日期、数字和其他文化敏感的信息。Java提供了...

    java中文问题分析

    4. **国际化与本地化**:Java提供`java.text`包,支持`Format`、`Collator`等类处理多语言环境下的日期、数字和文本排序等问题。 为了解决Java中的中文问题,开发者需要遵循以下原则: - 明确源代码的编码格式,并...

    javamelody-javamelody-core-1.67.0.zip

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

    JAVA中文问题解析和最优解决办法

    ### JAVA中文问题解析和最优解决办法 #### 一、引言 随着信息技术的快速发展与全球化进程的加速,软件开发越来越需要支持多语言环境,特别是中文环境。Java作为一门跨平台的编程语言,广泛应用于Web应用、企业级...

    Java国际化编程及其实现.rar

    通过分析和学习这些实例,开发者能更深入地理解Java国际化的工作原理,并能在自己的项目中有效地应用。 总的来说,Java的国际化特性使得开发者能够创建全球化的产品,满足不同地区用户的需求。熟练掌握这一技术对于...

    Java中的中文编码问题

    - **字符串处理**:在处理字符串时,特别是进行国际化和本地化操作时,需要特别注意编码问题。 #### 四、中文问题的原因分析 - **编码不一致**:这是最常见的问题之一。如果在不同环节使用了不同的编码格式,就会...

    Android应用源码之(本地化与国际化)-IT计算机-毕业设计.zip

    在Android应用开发中,本地化和国际化是两个重要的概念,它们涉及到如何使应用程序适应不同语言和地区的需求。这个压缩包中的内容很可能是为了帮助学生或开发者理解如何在Android平台上实现这一功能,以创建一个能为...

    java中文转拼音

    4. **国际化支持**:考虑到中文方言和不同地区的发音差异,开发一个支持多种方言发音的拼音转换器也是一个值得探讨的方向。 综上所述,`ChineseToEnglish`类提供了一种简单而有效的中文转拼音方案,适用于需要进行...

    Java中文编码问题研究.pdf

    3. 使用Java的internationalization支持:Java提供了国际化支持,例如ResourceBundle和MessageFormat等,可以用来解决中文编码问题。 四、结论 Java中文编码问题是Java程序中常见的问题,解决这个问题需要了解Java...

    Java中文乱码问题研究.pdf

    这是因为Java语言在设计之初就考虑到了国际化的问题,但是,实际开发中,中文乱码问题仍然存在。 针对客户端和服务器端传输数据,客户端显示中文字符编码,及应用程序与数据库之间的数据交互等问题,分析了Java乱码...

    Java语言的中文处理问题完整解决方案

    在Java编程语言中,中文处理问题...通过上述方法,开发者可以有效地解决Java程序中的中文乱码问题,确保程序的稳定性和国际化能力。对于更深入的学习,可以参考《Java核心技术卷》等相关书籍,以及查阅Oracle官方文档。

    Java JDK1.6中文版

    - **AWT和Swing的国际化**:对中文等多语言环境的支持更加完善。 5. **库更新**: - **Java EE支持**:对Java企业版(Java EE)的API进行了升级,为Web服务和企业级应用开发提供了更好的支持。 - **XML处理**:...

    Java类库中文手册

    10. **国际化和本地化**:`java.text`和`java.util.Locale`类支持多语言环境下的软件开发,使程序能适应不同地区的文化和语言习惯。 这份中文手册不仅介绍了上述各个部分的主要类和接口,还可能包括了示例代码、...

    Java中文帮助 chm

    还有国际化(i18n)和本地化(l10n)的指南,这对于开发面向全球市场的软件非常有用。 最后,这个压缩包中的"codefans.net"可能是指一个网站或者论坛,它可能提供了更多关于Java学习和交流的资源,例如代码示例、...

    java获取乱码问题

    ### Java获取乱码问题解析与解决方案 在Java应用开发过程中,字符编码问题一直是困扰开发者的一大难题,尤其是...此外,对于更复杂的多语言环境,还应该考虑采用国际化和本地化策略,以更好地支持不同地区的用户需求。

    JAVA中汉字字符转化为英文字符

    - **国际化**:在处理多语言环境时,这种转换可以帮助统一不同语言的处理流程。 - **数据分析**:对于大量包含中文字符的数据集,进行此类转换可以简化数据清洗和预处理步骤。 #### 总结 本文介绍了如何在Java中...

    java编写的简单汉字系统

    - `java.text`包提供了对国际化和本地化(i18n, L10n)的支持,包括字符编码和格式化,这对于处理汉字尤为重要。 2. **字符编码** - 在Java中,处理汉字通常涉及到不同的字符编码,如GBK、GB2312、UTF-8等。`...

Global site tag (gtag.js) - Google Analytics