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

下载本文档

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

文档简介

全面分析Java相关的“编码”问题本文说明Java程序员遇到的一个很头痛的问题就是编码问题。一旦开发与系统平台及网络相关的程序而需要传输中文字符时,若不能正确掌握编码的知识,将频频出现乱码问题而扰乱开发计划。目前已经存在大量的关于编码问题的讲解。个人觉得这些文档主要是教“怎样解决目前问题”而非“为什么有这个问题”。希望本文能够帮助读者从根本上了解编码原理,以及软件开发过程中出现乱码的原因,而非仅仅是解决某个具体问题。字符集与编码标准字符集UNICODE从概念上讲,字符集并不完全等同与编码标准,这两个概念的区别很模糊。(未经过文献证实,只是以掌握的知识这么认为)。大家都清楚Java使用的是UNICODE字符集。请注意这里使用的是“字符集”关键字。下面以UNICODE为例浅谈字符集与编码标准。UNICODE是一套字符集而非编码标准。UNICODE字符集并非一直不变,目前流行使用的版本是使用2个字节来存储理论数量为256x256=65535(个)的字符集。两个字节能表示的范围也就是这个数字,以16进制表示就是0x0000-0xFFFF。这65535(个)已经足够收录目前世界上主要语言的大多数字符了,保证日常通信是没问题的。最新的UNICODE标准已经可以支持几百万个字符了,当然随之而来的则是一个字符占用的字节数将更大。在目前使用的JRE版本中,一个中文字符是使用两个字节的内存空间来存储的。在Java中测试UNICODE的相关功能是非常容易的:首先是查看一个中文字符的UNICODE编码值(特别注意,这里指的是字符集中一个字符所代表的数字,它是存放在内存中的)。运行如下代码将可以得到:charch1='汉';intnch1=(int)ch1;System.out.println("汉:"+Integer.toHexString(nch1));运行结果为:汉:6c49。也就是说“汉”这个字符在内存中实际上是占用了两个字节的空间,一个字节存放的是“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系统的运行其实到处都存在转码的处理。因为不可能数据只存在于内存中吧,只在内存中处理就没任何意义了,用户需要对数据进行保存,读取,打印,输入等等操作,每一样操作都是存在“转码”处理的,只是一般我们不会去关心,JRE已经帮你做好了很多事。下面进入下一个重点知识,JRE与操作系统字符编码转换。Windows操作系统默认支持的是GBK,它是一种编码标准。GBK我没深入研究过,它肯定是一种编码标准,因为它的编码可以写入文件。但是不是字符集笔者也不曾深入研究。我们不讨论它的编码原理,只需要知道它是和UNICODE不一样的,也就是说同一个字符在它们之间存放的十六进制数是不一样的。JRE运行在WINDOWS中。我们从键盘输入的字符是以windows默认的编码标准接收的,也就是你输入的任何东西先是进入到操作系统又才会进入JRE。操作系统读取数据的时候内存中存放的是操作系统的默认支持的编码标准。这种编码格式不是JRE默认支持的。所以数据再进入JRE的时候就需要一次转码。java.nio.charset.Charset.defaultCharset().name()方法可以得到当前操作系统默认使用的编码标准。JRE与操作系统之间进行数据通信时,在未特别指定的情况下都以这个编码进行转化,实际上就是UNICODE和这个方法返回的结果的那个编码进行转换。通常情况下,当JRE读取控制台输入的字符时不需要进行编码指定,因为控制台接收数据后所存放的编码不会被用户更改,所以JRE只要用系统默认的编码进行转化就可以正确处理。(这个过程是:操作系统中存在一个字符串,它是以GBK编码的。而JRE需要将它读到JRE的内存里头,就需要通过JRE里面已经编写好了的GBK->UNICODE算法进行转化。String类中提供了转化的方法,详细请看java.lang.String类的多种构造方法)。当处理的是文件的时候就复杂一点了,因为文件中字符串的编码并不肯定是系统默认的编码。这个时候用户在读取的时候可以指定某一种编码来读取,使用HYPERLINKF:\JAVADOC\J2SE\zh\api\java\io\InputStreamReader.html;byte[]str_utf8_bytes=str.getBytes("UTF-8");得到的str_utf8_bytes则是“汉”这个字用UTF-8编码时的字节数组。它的值经过测试得到结果是{-26,-79,-119}。也就是说,当“汉”这个字以UTF-8编码标准写入文件时,它占用了三个byte的大小,分别是-26,-79,-119。通过这三个数字组成一个字节数组,是可以还原得到一个汉字“汉”的。代码如下:byte[]bytes_utf8=newbyte[]{-26,-79,-119};//创建字节数组Stringstr=newString(bytes_utf8,"UTF-8");//使用字节数组创建字符串,并且告诉系统使用“UTF-8”编码标准类解析这个字节数组。System.out.println("字节数组{-26,-79,-119}表示汉字:"+str);输出结果:字节数组{-26,-79,-119}表示汉字:汉。其中这三个数字都是以十进制表示的整数,如果想以十六进制查看只需要用Integer.toHexString就可以转化成十六进制了,可以测试,它们与“6C”和“49”这两个“汉”的UNICODE码差了很远。使用同样方法,可以分别得到“汉”这个字使用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路径中如果出现中文,形如/pro/查询.jsp的URL字符串,浏览器访问的是一个中文文件名的JSP文件“查询.jsp”。浏览器在发送请求的时候会将路径中的中文经过转码。首先假设浏览器以UTF-8转码。“查询”这两个汉字得到的字节数组为{-26,-97,-91,-24,-81,-94}。这里-26是个负数,并不能直接转化成十六进制。这其中涉及到数字编码的问题(原码、反码和补码的知识是计算机组成原理中涉及的)。只需要知道,浏览器将这六个数字转化成了六组十六进制字符串,且一个字符串只有两个字符,并且最后以“%”来连接。转化的结果是“%E6%9F%A5%E8%AF%A2”。也就是说,转码之后出现的每一个字节,都以%XX的形式表示,XX表示这个字节所代表的十六进制数。注意一个问题,26和-26转化成的结果肯定是不一样的。至于这里到底怎么转的,需要详细了解原码、反码和补码的知识。以范围来说0x00-0xFF表示的范围刚好是一个字节表示的范围,而一个字节表示的整数范围是-128到127。[-128,127]与[0x00,0xFF]是有一个对应关系的,并且是一对一的关系。不必去深究这里是怎么转化的,在Java中提供了现成的方法完成这个转化过程,.URLEncoder.encode方法可以完成转化。(查看附录)关键的问题是这样的:浏览器发送一个请求的时候,一次请求是一个数据包。这个数据包中同时包含了访问的URL路径(中文经过转码),还有URL后面以“?”连接的参数(如果参数有中文也会转码),还加上页面中的表单。比如如下的表单就会出现这个问题<formaction=”用户登陆.jsp?username=张三”> <input…/> <input…/><inputtype=”submit”…/></form>当用户输入了中文数据点了提交。这一次请求中三个存放数据的区域都出现了中文。为什么会频频造成乱码呢?下面将阐述:第一部分,URL路径部分的中文以何种编码标准进行转码,并不是程序中能够设置的。而是浏览器默认的。注意!注意!这是一个严重的问题,也就是说浏览器无论访问哪个网址,出现中文的时候都是以自己“愿意”的编码标准进行转码。可以通过设置来修改浏览器在转化URL路径时使用的编码标准。如IE的设置方式是:打开internet选项。在高级里面,有一个“国际”分类,里面有一个复选框“发送UTF-8URL”。如果勾上,浏览器将“始终”以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>Inserttitlehere</title> </head> <body> </body></html>其中有三个设置编码的地方,按顺序分别取名A,B和C。其中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>Inserttitlehere</title>"); out.append("</head>"); out.append("<body>"); out.append("<div>写个汉字看看</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\">");之前的代码中不应该出现中文,浏览器要读到这里的时候才会修改自己读取数据的方式。实际开发中,<title>标签要放在<meta>标签之后。但是这个地方的配置只在纯.html文件中起作用,或者在Servlet中未设置Response.setContentType的情况下,而在JSP中会被<%@pagecontentType="text/html;charset=utf-8">覆盖,后面有详细介绍。在JSP页面中的HTML部分指定的编码方式是无效的,因为<%@pagelanguage="java"contentType="text/html;charset=utf-8"%>已经指定了。也就是说浏览器会根据这一条命令指定的编码来读取服务器传回来的数据,同时也指定了Servlet在发送数据的时候使用的编码。如果笔者有兴趣,可以做查相关资料。服务器返回给浏览器的数据中除了HTML之外还包含很多数据的header。Response.setContentType设置的数据就是存放在这些header里头的。这一条指令在jsp转化成java文件之后,变成了代码“response.setContentType("text/html;charset=utf-8");”。如果这里未设置,那么浏览器会根据HTML里面<meta>中指定的编码方式来解析。比如说,在一个.html页面中,肯定没得response.setContentType。<html> <head> <metahttp-equiv="Content-Type"content="text/html;charset=utf-8"> <title>Inserttitlehere</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=newFileOutputStream("test.html"); PrintWriterout=newPrintWriter(newOutputStreamWriter(writer,"utf-8")); out.append("<html>"); out.append("<head>"); out.append("<metahttp-equiv=\"Content-Type\"content=\"text/html;charset=gbk\">"); out.append("<title>Inserttitlehere</title>"); out.append("</head>"); out.append("<body>"); out.append("<div>写个汉字看看</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/1.1"maxThreads="150"connectionTimeout="20000"redirectPort="8443" URIEncoding="utf-8"/>最后一项就是告诉服务器该如何来解析浏览器访问的URL,无论是路径部分还是参数部分都一样。这样就出现了一个问题值得注意,浏览器对路径部分和参数部分的编码方式不一定是一样的。而浏览器解析的时候又会以一种方式处理。很多中文网站都会将所有地方的编码设置成GBK,在所有设置编码格式的地方都设置GBK的情况下,有一个问题必须避免,那就是URL路径部分不能有中文出现,否则出错。因为浏览器还是会以UTF-8来编码路径部分,以GBK编码参数部分和页面中的表单。而如果服务器server.xml中配置的编码是GBK的话,服务器会用GBK来解析URL。殊不知这里是URL其实同时包含了UTF-8编码的路径部分和GBK编码的参数部分。服务器获取数据时用得最多的方法莫过于request.getParameter()了。request.setCharacterEncoding()这个方法可以设置读取数据使用的编码。而这个方法设置的编码只能影响表单部分,并且只对POST方式起作用。因为GET方式的参数是放在URL中的。URL的解析方式是配置文件决定的。所以如果你的参数带有中文,并且服务器配置了正确的编码来解析URL,那么这里直接request.getParameter()的结果就是你需要的字符串,也不需要转码了。大家都知道,表单部分发送过来的参数是需要转一次码才能正确显示的。通常都是用的这个方法request.getParameter().getBytes("ISO-8859-1");然后在将取得的字节数组作为参数,使用String的构造方法进行转换。比如byte[]buffer=request.getParameter(“name”).g

温馨提示

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

评论

0/150

提交评论