全面分析Java相关的“编码”问题_第1页
全面分析Java相关的“编码”问题_第2页
全面分析Java相关的“编码”问题_第3页
全面分析Java相关的“编码”问题_第4页
全面分析Java相关的“编码”问题_第5页
已阅读5页,还剩15页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

全面分析Java相关的“编码”问题

本文说明

Java程序员遇到的一个很头痛的问题就是编码问题。一

旦开发与系统平台及网络相关的程序而需要传输中文字符

时,若不能正确掌握编码的知识,将频频出现乱码问题而扰

乱开发计划。目前已经存在大量的关于编码问题的讲解。个

人觉得这些文档主要是教“怎样解决目前问题”而非“为什

么有这个问题”。希望本文能够帮助读者从根本上了解编码

原理,以及软件开发过程中出现乱码的原因,而非仅仅是解

决某个具体问题。

字符集与编码标准

字符集UNICODE

从概念上讲,字符集并不完全等同与编码标准,这两个

概念的区别很模糊C(未经过文献证实,只是以掌握的知识

这么认为)。大家都清楚Java使用的是UNICODE字符集。请

注意这里使用的是“字符集”关键字。下面以UNICODE为例

浅谈字符集与编码标准。

UNICODE是一套字符集而非编码标准。UNICODE字符集

并非一直不变,目前流行使用的版本是使用2个字节来存储

理论数量为256x256=65535(个)的字符集。两个字节能表示的

范围也就是这个数字,以16进制表示就是OxOOOO-OxFFFFo

这65535(个)已经足够收录目前世界上主要语言的大多数字

符了,保证日常通信是没问题的。最新的UNICODE标准已经

可以支持几百万个字符了,当然随之而来的则是一个字符占

用的字节数将更大。

在目前使用的JRE版本中,一个中文字符是使用两个字

节的内存空间来存储的。在Java中测试UNICODE的相关功

能是非常容易的:

首先是查看一个中文字符的UNICODE编码值(特别注

意,这里指的是字符集中一个字符所代表的数字,它是存放

在内存中的)。运行如下代码将可以得到:

charchi二汉;

intnchl=(int)chl;

System.out.println("yX:"+Integer.toHexString(nchl));

运行结果为:汉:6c41

也就是说“汉”这个字符在内存中实际上是占用了两个

字节的空间,一个字节存放的是“6C”而另一个字节存放的

是“49”。这个十六进制数“0x6c49”所代表的数字则是在

UNICODE字符集标准中“汉”这个字对应的数字值。

Java中提供了对“转码”相当好的支持,使用的时候也

非常方便。请运行下面的代码:

Stringstr="\u6c49〃;

System.out.println("6C49->"+str);

运行结果:6c49->汉。

“\u”是将十六进制数转化成字符的转义字符。它只对

它后面紧跟上的四个字符起作用。比如说:(‘a'的UNICODE

数字编码是61)

Stringstr=〃\u61”;〃这句代码是通不过编译的,"\u

后面至少要4位十六进制的数字字符〃

Stringstr=〃\ugf61〃;〃代码无法通过编译,提示不正

确的UNICODE编码,

Stringstr=〃\u0061”;〃代码能够运行,打str的结

果为〃a〃。

在JRE的bin目录下有一个native2ascii.exe程序,它可

以提供对中文字符的转码,转化成上述的\uXXXX的格式。因

为这种格式的字符串可以在完全不支持中文的环境中存储

和传输,并且也可以被Java程序轻松的还原(Stringstr=

〃\u6c49〃;这样就还原了,够简单了)。实际上手工编写一个

native2ascii.exe的功能也是很简单的,前面已经介绍了其中

的原理了。

Java中UNICODE字符集是表示在内存中的字符的编码,

是和String类紧密相关的。一旦一个字符串建立,这些字符

在内存中所存放的十六进制数就是根据UNICODE而来。在不

需要写入文件和网络传输的情况下它的编码都是统一的。换

句话说String类在处理字符的时候会将对象自身存储的“字

节数组”以UNICODE来分析,两两字节来读取,将两个字节

作为一组当成一个字符来处理。(注意这里)

这种不需要转换编码的字符串处理过程是程序员非常

希望见到的。但实际上Java系统的运行其实到处都存在转码

的处理。因为不可能数据只存在于内存中吧,只在内存中处

理就没任何意义了,用户需要对数据进行保存,读取,打印,

输入等等操作,每一样操作都是存在,,转码”处理的,只是

一般我们不会去关心,《IRE已经帮你做好了很多事。

下面进入下一个重点知识,JRE与操作系统字符编码转

换。

Windows操作系统默认支持的是GBK,它是一种编码标

准。GBK我没深入研究过,它肯定是一种编码标准,因为它

的编码可以写入文件。但是不是字符集笔者也不曾深入研

究。我们不讨论它的编码原理,只需要知道它是和UNICODE

不一样的,也就是说同一个字符在它们之间存放的十六进制

数是不一样的。

JRE运行在WINDOWS中。我们从键盘输入的字符是以

windows默认的编码标准接收的,也就是你输入的任何东西

先是进入到操作系统又才会进入JREo操作系统读取数据的

时候内存中存放的是操作系统的默认支持的编码标准。这种

编码格式不是JRE默认支持的。所以数据再进入JRE的时候

就需要一次转码。

java.nio.charset.Charset.defaultCharset(),name()7f&nJ以

得到当前操作系统默认使用的编码标准。JRE与操作系统之

间进行数据通信时,在未特别指定的情况下都以这个编码进

行转化,实际上就是UNICODE和这个方法返回的结果的那个

编码进行转换。

通常情况下,当JRE读取控制台输入的字符时不需要进

行编码指定,因为控制台接收数据后所存放的编码不会被用

户更改,所以JRE只要用系统默认的编码进行转化就可以正

确处理。(这个过程是:操作系统中存在一个字符串,它是

以GBK编码的。而JRE需要将它读到川E的内存里头,就需

要通过JRE里面已经编写好了的GBK->UNICODE算法进行转

化。String类中提供了转化的方法,详细请看java.Iang.String

类的多种构造方法)。

当处理的是文件的时候就复杂一点了,因为文件中字符

串的编码并不肯定是系统默认的编码。这个时候用户在读取

的时候可以指定某一种编码来读取,使用InputStreamReader

类,它有设置编码的构造方法。

由于JRE封装好了UNICODE与系统平台之间的编码转

换,所以对于不经常使用编码的人员来说,编码问题就不重

要了。

UTF-8编码

Java中经常使用的编码标准莫过于“UTF-8”了。它是编

码标准而非单单是字符集,它也不是UNICODE。UNICODE中

是以固定长存放字符的,而UTF-8是变长的。这其中又有什

么关系呢?其实UTF-8和GBK、GB2312等等属于同一类,只

是一种编码标准而已。它的功能是将内存中的字符保存到硬

盘或者发送到网络传输线的时候使用的编码。这里需要注意

理解一点,UNICODE字符集所用来表示字符的编码是“不能”

直接写入硬盘和发送当网络传输线上去的。也就是说“6c49”

这个4个十六进制字符所代表的字符是“汉”,但如果将“汉”

这个字写入到文件,无论以哪种编码格式写入,在文件里保

存的二进制编码中绝对不是6c49所表示的二进制编码。(以

上为个人理解和测试的结果)。

内存中的“汉”这个字,所占用的两个字节里面存放的

是“6c”和“49”。如果要以UTF-8格式写入到文件中,它的

字节数组将是什么呢?下面通过测试得到这个字节数组的

值。(具体这些编码标准什么编码的不是本文讨论的问题,因

为它怎么编其实并不直接影响乱码的出现,而是编码之间的

转化才导致了乱码),

Stringstr="汉”;

byte[]str_utf8_bytes=str.getBytes("UTF-8");

得到的str_utf8_bytes则是“汉”这个字用UTF-8编码时

的字节数组。它的值经过测试得到结果是{-26,-79,也

就是说,当“汉”这个字以UTF-8编码标准写入文件时,它

占用了三个byte的大小,分别是・26厂79,-119。通过这三个数

字组成一个字节数组,是可以还原得到一个汉字“汉”的。

代码如下:

byte[]bytes_utf8=newbyte[]{-26,-79,-119};〃创建字节

数组

Stringstr=newString(bytes_utf8,"UTF-8");〃使用字节

数组创建字符串,并且告诉系统使用“UTF-8”编码标准类解

析这个字节数组。

System.out.printin("字节数组{-26,・79,-119}表示汉字:”+

str);

输-结果:字节数组{-26,.79,;19}表示汉字:汉

其中这三个数字都是以十进制表示的整数,如果想以十

六进制查看只需要用Integer.toHexString就可以转化成十六

进制了,可以测试,它们与“6C”和“49”这两个“汉”的

UNICODEii马差了很远。

使用同样方法,可以分别得到“汉”这个字使用GBK编

码时所得到的字节数组

Stringstr="汉”;

byte[]str_gbk_bytes=str.getBytes("GBK");

得到的字节数组为{-70.70},结果告诉我们“汉”这个字

以GBK编码的时候产生了两个字节,这两个字节分别存放的

是.70和-70。同样可以使用上面的方法还原,读者可以自己

测试。

注意:\uXXXX格式是Java常用的。它这种格式转化和这

里的编码并不是一回事。因为不可能将\uXXXX直接写入文

件。写入文件之后可能Java系统可以识别,但其他平台就未

必了。而使用UTF-8和GBK则可以和其他平台进行通信。

特别强调:同一个字使用不同的编码得到的字节数组的

值可能不一样(这样说是因为有些编码之间有兼容关系,可

能是一样的),虚拟机中的字符串并不是以某种可写入文件

的编码格式存储的,而是以UNICODE值存放的。要写入文件

或者发送到网络传输线上面都是需要进行转码的,转码的方

法就是String.getBytes。方法,给一个编码名称做参数就得到

了该编码下这个字符串的字节数组了。还原的时候是使用

String的构造方法,但是你必须知道这个字节数组是以哪种

格式编码的,不然系统不能正确读取。刚才已经得知{-70,70}

这个字节数组是“汉”这个字以GBK编码时得到的字节数组。

如果你使用Stringstr=newString(newbyte[]{-70,-70}//utf-8,/)

来还原,肯定是会出乱码的。

以上就介绍完了JRE之中关于编码的知识,也简单介绍

了编码与编码之间的转化。UNICODE可以作为一个中间标准,

因为JRE已经完成了UNICODE与任意编码之间的转换功能,

这样就可以完成不同编码之间的转化功能。

WEB系统中编码问题的出现

JSP页面中设置编码

在WEB系统的通信模式中,出现编码转换的地方很多。

浏览器向服务器发送一次请求的数据包中,就有三个地方涉

及到编码问题。

它们分别是:URL、URL后面的参数以及表单。

通常情况下URL和URL后面的参数都不会出现中文字

符,在没有中文字符的时候是不存在乱码问题的,因为所有

编码标准中对于英文字符的码值都是一样的。而当URL和

URL中参数出现中文的时候,编码问题就相当棘手。

首先是URL路径中如果出现中文,形如

的uRL字符串,浏览器访

问的是一个中文文件名的JSP文件"查询.jsp”。浏览器在发

送请求的时候会将路径中的中文经过转码。首先假设浏览器

以UTF-8转码。“查询”这两个汉字得到的字节数组为

{-26,-97,-91,-24,-81,-94}0这里-26是个负数,并不能直接转化

成十六进制。这其『涉及到数字编码的问题(原码、反码和

补码的知识是计算机组成原理中涉及的)。只需要知道,浏

览器将这六个数字转化成了六组十六进制字符串,且一个字

符串只有两个字符,并且最后以“%”来连接。转化的结果

是“%E6%9F%A5%E8%AF%A2”。也就是说,转码之后出现的

每一个字节,都以%xx的形式表示,XX表示这个字节所代表

的十六进制数。注意一个问题,26和-26转化成的结果肯定

是不一样的。至于这里到底怎么转的,需要详细了解原码、

反码和补码的知识。以范围来说OxOO-OxFF表示的范围刚好

是一个字节表示的范围,而一个字节表示的整数范围是;28

到127o卜128,12刀与[0x00,OxFF]是有一个对应关系的,并且

是一对一的关系。不必去深究这里是怎么转化的,在Java中

提供了现成的方法完成这个转化过程,

.URLEncoder.encode方法可以完成转化。(查看附录)

关键的问题是这样的:浏览器发送一个请求的时候,一

次请求是一个数据包。这个数据包中同时包含了访问的URL

路径(中文经过转码),还有URL后面以“?”连接的参数(如

果参数有中文也会转码),还加上页面中的表单。比如如下的

表单就会出现这个问题

<formaction=”用户登陆.jsp?username二张三〃〉

<input•••/>

<input・・・/>

<inputtype="submit”•••/>

</form>

当用户输入了中文数据点了提交。这一次请求中三个存

放数据的区域都出现了中文。为什么会频频造成乱码呢?下

面将阐述:

第一部分,URL路径部分的中文以何种编码标准进行转

码,并不是程序中能够设置的。而是浏览器默认的。注意!

注意!这是一个严重的问题,也就是说浏览器无论访问哪个

网址,出现中文的时候都是以自己“愿意”的编码标准进行

转码。可以通过设置来修改浏览器在转化URL路径时使用的

编码标准。如IE的设置方式是:打开internet选项。在高级

里面,有一个“国际”分类,里面有一个复选框“发送UTF.8

URL*如果勾上,浏览器将“始终”以UTF-8进行转码。如

果未勾上,则会同第二部分的编码方式一样。不同的浏览器

处理的方式还不一样,值得注意!

第二部分,URL路径后面跟随的参数部分使用的编码方

式又不一样了,这个编码方式是根据当前的HTML页面决定

的。通常访问一个新的URL都是以表单发送或者超级链接,

这样前一个HTML页面中指定的编码方式将决定这次请求时

参数的编码方式。如果是首次访问人工输入的URL,则浏览

器以默认编码方式进行转码。

第三部分,表单部分浏览器以何种编码方式发送数据,

下文将有介绍。

总结来说,URL路径部分以何种格式进行编码是浏览器

自己决定的,而参数部分和表单部分同时是根据当前页面的

语言环境决定的。那么是页面的哪个设置决定了的呢?见如

下JSP代码:

<%@pagelanguage=“java”contentType="text/html;

charset=utf-8"

pageEncoding="gbk"%>

<html>

<head>

<metahttp-equiv="Content-Type"content="text/html;

charset=utf-8">

<title>lnserttitlehere</title>

</head>

<body>

</body>

</html>

其中有三个设置编码的地方,按顺序分别取名A,B和Co

其中A处和B是在<%@page…〉中的两个属性,第一个是

contentType="text/html;charset=utf-8",第二个是

pageEncoding二"gbk”。这两个分别什么含义呢?这两个到底哪

个决定了上述的浏览器的编码方式呢。答案是第一个。

第一个charset=utf-8的设置,告诉浏览器当前页面使用

utf-8对URL后面的参数部分进行转码,并且同时以UTF-8发

送表单。

第二个pageEncoding=“gbk”它的作用范围并不涉及到浏

览器。它的作用效果是决定当前JSP文件保存到操作系统中

以何种编码方式编码;并且告诉WEB服务器,在读取到这个

JSP页面的时候该也何种编码读取。如果当pageEncoding未

设置的时候,系统会参考第一个contentType="text/html;

charset=utf-8”的编码。如果都未设置,当页面又有中文的时

候,JSP页面根本无法保存,不信你就试试看。

第三个是HTML部分设置的编码方式。它是告诉浏览器

取得从服务器返回回来的字节数组时,解析字节数组该使用

的编码方式。就是浏览器将使用这个编码方式解析从网络上

取得的字节数组而转化成HTML字符串。查看HTML文档,

其中对charset的解释为“设置或获取用于解码对象的字符

集”。Servlet测试代码如下:

response.setCharacterEncoding("UTF-8");

Printwriterout=response.getWriter();

out.append("<html>");

out.append("<head>");

out.append("<metahttp-equiv=\"Content-Type\"

content=\"text/html;charset=gbk\">");

out.append("<title>lnserttitlehere</title>");

out.append("</head>");

out.append("<body>");

out.append("<div>^^i%^§§</div>");

out.append("</body>");

out.append("</html>");

out.flush();

out.close();

当response中指定的编码方式和HTML设置的编码方式

不一样时,浏览器无法正确显示数据。也就是说WEB服务器

向浏览器发送的数据是以UTF-8编码的,而浏览器读取的时

候是以GBK读取,肯定无法正确显示。这里又说明一个问题,

在out.append("<metahttp-equiv=\"Content-Type\"

content=\"text/html;charset=gbk\">");之前的代码中不应该出

现中文,浏览器要读到这里的时候才会修改自己读取数据的

方式。实际开发中,标签要放在<meta>标签之后。

但是这个地方的配置只在纯.html文件中起作用,或者在

Servlet中未设置Response.setContentType的情况下,而在JSP

中会被<%@pagecontentType="text/html;charset=utf-8">覆

盖,后面有详细介绍。

在JSP页面中的HTML部分指定的编码方式是无效的,

因为<%@pagelanguage=njava"contentType="text/html;

charset=utf-8"%>已经指定了。也就是说浏览器会根据这一条

命令指定的编码来读取服务器传回来的数据,同时也指定了

Servlet在发送数据的时候使用的编码。如果笔者有兴趣,可

以做查相关资料。服务器返回给浏览器的数据中除了HTML

之外还包含很多数据的设

headeroResponse.setContentType

置的数据就是存放在这些header里头的。这一条指令在jsp

转化成java文件之后,变成了代码

uresponse.setContentType("text/html;charset=utf-8");,,o如果

这里未设置,那么浏览器会根据HTML里面<meta>中指定的

编码方式来解析。

比如说,在一个.html页面中,肯定没得

response.setContentType。

<html>

<head>

<metahttp-equiv="Content-Type"content="text/html;

charset=utf-8">

<title>lnserttitlehere</title>

</head>

<body>

</body>

</html>

此时浏览器会使用<meta>里面配置的编码方式来发送

当前表单和对URL进行转码。如果是JSP页面生成的HTML

中的<metahttp-equiv="Content-Type"content="text/html;

charset=utf-8">通常是无效的。因为浏览器会以

response.setContentType的值为准,这个值是放在返回时数

据包中的header中的,查看网页源代码是看不到的。

那为什么还要HTML中可以指定一个编码格式呢。这是

因为HTML还有另一个用途,它可以直接保存到操作系统中

由浏览器直接读取,而非从网络上获取。浏览器访问WEB

服务器的时候,服务器返回数据之前就告诉了浏览器该用何

种编码格式解析我发给你的数据。浏览器直接从操作系统中

读取一个HTML文件的时候就不同了,没人告诉我。那么我

就要从HTML中的<metahttp-equiv=\"Content-Type\"

content=\"text/html;charset=gbk\'、这段代码去判断。

FileOutputStreamwriter=new

FileOutputStream("test.html");

PrintWriterout=newPrintWriter(new

OutputStreamWriter(writer;'utf-8"));

out.append("<html>");

out.append("<head>");

out.append("<metahttp-equiv=\"Content-Type\"

content=\"text/html;charset=gbk\">");

out.append("<title>lnserttitlehere</title>");

out.append("</head>");

out.append("<body>");

out.append("<div>^^i%^§§</div>");

out.append("</body>");

out.append("</html>");

out.flush();

out.close();

通过上面代码测试,写入一个HTML文件到硬盘,并且

指定以utf-8编码格式写入文件。然后用浏览器打开这个

HTML文件,使用的是GBK进行解析,发现是乱码。如果这

两个编码是一致的,就不是乱码了。也就是说HTML中的编

码在这里起了作用。

服务器取得参数

URL的解析的时候使用的编码方式是在配置文件中设置

的。以TOMCAT为例,conf/server.xml中设置服务器解析URL

的编码方式。

<Connectorport="8080"protocol="HTTP/l.l"

maxThreads="150"connectionTimeout="20000"

redirectPort="8443"

URIEncoding="utf-8'7>

最后一项就是告诉服务器该如何来解析浏览器访问的

URL,无论是路径部分还是参数部分都一样。这样就出现了

一个问题值得注意,浏览器对路径部分和参数部分的编码方

式不一定是一样的。而浏览器解析的时候又会以一种方式处

理。很多中文网站都会将所有地方的编码设置成GBK,在所

有设置编码格式的地方都设置GBK的情况下,有一个问题必

须避免,那就是URL路径部分不能有中文出现,否则出错。

因为浏览器还是会以UTF-8来编码路径部分,以GBK编码参

数部分和页面中的表单。而如果服务器server.xml中配置的

编码是GBK的话,服务器会用GBK来解析URL。殊不知这里

是URL其实同时包含了UTF-8编码的路径部分和GBK编码的

参数部分。

服务器获取数据时用得最多的方法莫过于

request.getParameter()了。request.setCharacterEncoding()ii^

方法可以设置读取数据使用的编码。而这个方法设置的编码

只能影响表单部分,并且只对POST方式起作用。因为GET

方式的参数是放在URL中的。URL的解析方式是配置文件决

定的。所以如果你的参数带有中文,并且服务器配置了正确

的编码来解析URL,那么这里直接request.getParameter。的

结果就是你需要的字符串,也不需要转码了。

大家都知道,表单部分发送过来的参数是需要转一次码

才能正确显示的。通常都是用的这个方法

request.getParameter().getBytes("ISO-8859-l");然后在将取得

的字节数组作为参数,使用String的构造方法进行转换。比

byte[]buffer=

request.getPa

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论