`

(转)编码、乱码问题

阅读更多

一、编码进程

01编码】——很久很久以前,为了表示二极管的通、分,我们引入的高电平、低电平,之后又引入的10编码进行代替

 

ASCII编码】——很久以前,也就是上个世纪60年代,美国佬为了把计算机的“0101010”编码与文字进行对应起来,制定了一套ASCII编码方案。人总是自私的,他只对自己的语言进行编码,26个字母、数字、其他符号,只用了7位二进制数搞定,第一位用0表示,预留着。所以ASCII编码最多127编码

 

【“二代”ASCII编码】——不单单是美国佬想把计算机语言和英文联系起来,其他国家也想把自己的语言关联起来,我法文、俄文就基于ASCII编码,利用上ASCII第一位(未使用)变成1,来对自己国家的语言进行编码。

 

介样,每一个国家都基于ASCII,让第一位变成1,完成对本国的语言的编码。由于他们各干各的,没有沟通,从而导致了同一种编码出现不同的文字。

 

gb2312编码】——我们伟大的祖国80年代也开始对汉字进行编码,由于我们的文字较多,即使是基于ASCII将第一位变成1,也不够我们使用(国语博大精深拉),所以就制定了一套gb2312编码,使用2个字节表示。

 

BIG5编码】——我国的港澳地区,他们是使用繁体字(gb2312编码最初并没有考虑到繁体字),怎么办了?他们就出了自己的区域编码BIG5

 

【中国一统GBK编码】——为了统一汉字,迫切的需要设计出一种既能支持简体字又能表示繁体的新编码方案,GBK诞生了!他兼容了绝大部分gb2312编码(gb2312编码的文字用gbk可以读出来,但是不兼容BIG5编码)

 

【世界大统unicode编码】——各国编码各做各的,总不是意见好事,为了便于交流,国际社会引入了unicode——uni统一的意思,code编码)把所有国家的文字都进行了编码。

 

【统一后浪费空间问题】——统一是一件好事,但是也是有问题的。英文只要1个字节ASCII编码就可以表示,你unicode还需要2个字节或更多,导致unicode表示英文的时候前面有很多无用的000000000000000000,对吧。

 

【解决问题,UTF编码】——解决方法是使用utf8编码,它是基于unicode编码上的一种优化,英文使用1个字节,中文使用23个(绝大部分是3个,个别有看到24个的一般不用)字节。好处:我能屈能伸,我可以变化长度来保存,就不会浪费空间了吧。

 

 

 

 

来看看unicodeutf对应吧

<!--[if !supportLists]-->·         <!--[endif]-->0000 0000-0000 007F | 0xxxxxxx

<!--[if !supportLists]-->·         <!--[endif]-->0000 0080-0000 07FF | 110xxxxx 10xxxxxx

<!--[if !supportLists]-->·         <!--[endif]-->0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

<!--[if !supportLists]-->·         <!--[endif]-->0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

 

举例说明:

字的Unicode编码是0x6C490x6C490x0800-0xFFFF之间,使用用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001,用这个比特流依次代替模板后面的x,得到:11100110 10110001 10001001,十六进制E6 B1 89,转换成10进制就是230  这个就是我们的UTF8编码,"".getBytes("utf-8") 得到-26 -79 -119,是负数,也是跟这里有关

      Unicode        utf8

        0x6c49         0xe6b189

二、java编程的转码

   1JVM内存中保存的文字编码

       从源文件àclass文件过程中的转码情况。最终的class文件都是以unicode编码的,我们前面所做的工作就是把各种不同的编码转换为unicode编码,比如从GBK转换为unicode,UTF-8转换为unicode。因为只有采用正确的编码来转码才能保证不出现乱码。Jvm在运行时其内部都是采用unicode编码的,其实在输出时,又会做一次编码的转换。

 

比如:Sysout.out.println(“我们”)。经过正确的解码后我们unicode保存在内存中的,但是在向标准输出(控制台)输出时,jvm又做了一次转码,它会采用操作系统默认编码(中文操作系统是GBK),将内存中的unicode编码转换为GBK编码,然后输出到控制台。

参考文献:http://www.blogjava.net/zhangchao/archive/2011/05/26/351051.html

 

   2、爱也getBytes,恨也getBytes

       一般我们是通过getBytes来进行编码,如下

         public byte[] getBytes(String charsetName)       //使用规定的编码

         public byte[] getBytes(Charset charset) //使用规定的编码

         public byte[] getBytes() //使用启动JVM设置的编码(如果没有设置一般是系统默认编码),避免使用,默认编码和平台有关

 

    一般我们通过new String来进行解码,如下

       public String(byte bytes[], String charsetName)        

                  public String(byte bytes[], Charset charset)

                  public String(byte bytes[])

 

    原则1:使用某一种编码集对文字进行编码,就得使用同一种编码集进行解码

       比如:“汉”是在java文件中的,此java文件是utf-8或者gbk编码的

         getBytes使用utf-8编码,new String使用utf-8解密

    new String("".getBytes("utf-8"), "utf-8"));

       

    原则2:看清楚对方给你的是什么编码,使用逆过程进行编码、解码(像出栈操作),最终得到你想要文字

    比如:对方进行了如下操作,将“汉”进行utf-8编码,然后使用iso8859-1进行解码生成对应的文字:,最后通过文本发送给你。对方可能的操作应该是:

         String x = new String("".getBytes("utf-8"), "iso8859-1");

 

         我们需要对文本中文字x进行iso8859-1编码,得到的是“汉”utf-8的译码,然后我们在对“汉”进行解码得到其utf8的文字

    new String(x.getBytes("iso8859-1"),"utf-8"));

 

    原则3:慎用getBytes(),他是基于平台的,你得看看JVM启动的时候file.encoding这个属性值到底是什么(请注意tomcat的启动JVM时候设置的编码)

    源码:特别注意黄底部分

staticbyte[] encode(char[] ca, int off, int len) {

    String csn = Charset.defaultCharset().name();

    try {

        return encode(csn, ca, off, len);

    } catch (UnsupportedEncodingException x) {

        warnUnsupportedCharset(csn);

    }

    try {

        return encode("ISO-8859-1", ca, off, len);

    } catch (UnsupportedEncodingException x) {

        // If this code is hit during VM initialization, MessageUtils is

        // the only way we will be able to get any kind of error message.

        MessageUtils.err("ISO-8859-1 charset not available: "

                + x.toString());

        // If we can not find ISO-8859-1 (a required encoding) then things

        // are seriously wrong with the installation.

        System.exit(1);

        returnnull;

    }

publicstatic Charset defaultCharset() {

        if (defaultCharset == null) {

        synchronized (Charset.class) {

       java.security.PrivilegedAction pa =

           new GetPropertyAction("file.encoding");

       String csn = (String)AccessController.doPrivileged(pa);

       Charset cs = lookup(csn);

       if (cs != null)

           defaultCharset = cs;

                else

           defaultCharset = forName("UTF-8");

            }

    }

    return defaultCharset;

}

}

-Dfile.encoding解释:
在命令行中输入java,在给出的提示中会出现-D的说明:
-D<name>=<value>
set a system property
-D
后面需要跟一个键值对,作用是通过命令行向java虚拟机传递一项系统属性
-Dfile.encoding=UTF-8来说就是设置系统属性file.encodingUTF-8

 

java.nio.charset包中的Charset.java中。这段话的意思说的很明确了,简单说就是默认字符集是在java虚拟机启动时决定的,后面无法动态改变了。默认字符集就是从file.encoding这个属性中获取的。

 

Java's file.encoding property on Windows platform
This property is used for the default encoding in Java, all readers and writers would default to using this property. file.encoding is set to the default locale of Windows operationg system since Java 1.4.2. System.getProperty("file.encoding") can be used to access this property. Code such as System.setProperty("file.encoding", "UTF-8") can be used to change this property. However, the default encoding can be not changed dynamically even this property can be changed. So the conclusion is that the default encoding can't change after JVM starts. java -dfile.encoding=UTF-8 can be used to set the default encoding when starting a JVM. I have searched for this option Java official documentation. But I can't find it.

 

参考文献:http://www.cnblogs.com/vigarbuaa/archive/2012/04/11/2442582.html

三、url网址的编码规范

一、url编码规范

一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。比如,世界上有英文字母的网址“http://www.abc.com”,但是没有希腊字母的网址“http://www.aβγ.com”(读作阿尔法-贝塔-伽玛.com)。这是因为网络标准RFC 1738做了硬性规定:

"...Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'()," [not including the quotes - ed], and reserved characters used for their reserved purposes may be used unencoded within a URL."

只有字母和数字[0-9a-zA-Z]、一些特殊符号“$-_.+!*'(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL

这意味着,如果URL中有汉字,就必须编码后使用。但是麻烦的是,RFC 1738没有规定具体的编码方法,而是交给应用程序(浏览器)自己决定。这导致“URL编码成为了一个混乱的领域。

 

参考文献:http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

四、浏览器的编码

下面就让我们看看,“URL编码到底有多混乱。我会依次分析四种不同的情况,在每一种情况中,浏览器的URL编码方法都不一样。把它们的差异解释清楚之后,我再说如何用Javascript找到一个统一的编码方法。

二、情况1:网址路径中包含汉字

打开IE(我用的是8.0版),输入网址http://zh.wikipedia.org/wiki/春节。注意,春节这两个字此时是网址路径的一部分。

 

查看HTTP请求的头信息,会发现IE实际查询的网址是http://zh.wikipedia.org/wiki/%E6%98%A5%E8%8A%82。也就是说,IE自动将春节编码成了“%E6%98%A5%E8%8A%82”

 

我们知道,utf-8编码分别是“E6 98 A5”“E8 8A 82”,因此,“%E6%98%A5%E8%8A%82”就是按照顺序,在每个字节前加上%而得到的。(具体的转码方法,请参考我写的《字符编码笔记》。)

Firefox中测试,也得到了同样的结果。所以,结论1就是,网址路径的编码,用的是utf-8编码。

三、情况2:查询字符串包含汉字

IE中输入网址http://www.baidu.com/s?wd=春节。注意,春节这两个字此时属于查询字符串,不属于网址路径,不要与情况1混淆。

 

查看HTTP请求的头信息,会发现IE春节转化成了一个乱码。

 

切换到十六进制方式,才能清楚地看到,春节被转成了“B4 BA BD DA”

 

我们知道,GB2312编码(我的操作系统“Windows XP”中文版的默认编码)分别是“B4 BA”“BD DA”。因此,IE实际上就是将查询字符串,以GB2312编码的格式发送出去。

Firefox的处理方法,略有不同。它发送的HTTP Head“wd=%B4%BA%BD%DA”。也就是说,同样采用GB2312编码,但是在每个字节前加上了%

 

所以,结论2就是,查询字符串的编码,用的是操作系统的默认编码。(我使用谷歌浏览器,他会是使用utf-8编码的)

四、情况3Get方法生成的URL包含汉字

前面说的是直接输入网址的情况,但是更常见的情况是,在已打开的网页上,直接用GetPost方法发出HTTP请求。

根据台湾中兴大学吕瑞麟老师的试验,这时的编码方法由网页的编码决定,也就是由HTML源码中字符集的设定决定。

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

如果上面这一行最后的charsetUTF-8,则URL就以UTF-8编码;如果是GB2312URL就以GB2312编码。

举例来说,百度是GB2312编码,GoogleUTF-8编码。因此,从它们的搜索框中搜索同一个词春节,生成的查询字符串是不一样的。

百度生成的是%B4%BA%BD%DA,这是GB2312编码。

 

Google生成的是%E6%98%A5%E8%8A%82,这是UTF-8编码。

 

所以,结论3就是,GETPOST方法的编码,用的是网页的编码。

 

参考文献:http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

五、网页、js编码

五、情况4Ajax调用的URL包含汉字

前面三种情况都是由浏览器发出HTTP请求,最后一种情况则是由Javascript生成HTTP请求,也就是Ajax调用。还是根据吕瑞麟老师的文章,在这种情况下,IEFirefox的处理方式完全不一样。

举例来说,有这样两行代码:

  url = url + "?q=" +document.myform.elements[0].value; // 假定用户在表单中提交的值是春节这两个字

  http_request.open('GET', url, true);

那么,无论网页使用什么字符集,IE传送给服务器的总是“q=%B4%BA%BD%DA”,而Firefox传送给服务器的总是“q=%E6%98%A5%E8%8A%82”。也就是说,Ajax调用中,IE总是采用GB2312编码(操作系统的默认编码),而Firefox总是采用utf-8编码。这就是我们的结论4

六、Javascript函数:escape()

好了,到此为止,四种情况都说完了。

假定前面你都看懂了,那么此时你应该会感到很头痛。因为,实在太混乱了。不同的操作系统、不同的浏览器、不同的网页字符集,将导致完全不同的编码结果。如果程序员要把每一种结果都考虑进去,是不是太恐怖了?有没有办法,能够保证客户端只用一种编码方法向服务器发出请求?

回答是有的,就是使用Javascript先对URL编码,然后再向服务器提交,不要给浏览器插手的机会。因为Javascript的输出总是一致的,所以就保证了服务器得到的数据是格式统一的。

Javascript语言用于编码的函数,一共有三个,最古老的一个就是escape()。虽然这个函数现在已经不提倡使用了,但是由于历史原因,很多地方还在使用它,所以有必要先从它讲起。

实际上,escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值。比如春节的返回结果是%u6625%u8282,也就是说在Unicode字符集中,是第6625个(十六进制)字符,是第8282个(十六进制)字符。

 

它的具体规则是,除了ASCII字母、数字、标点符号“@ * _ + - . /”以外,对其他所有字符进行编码。在\u0000\u00ff之间的符号被转成%xx的形式,其余符号被转成%uxxxx的形式。对应的解码函数是unescape()

所以,“Hello World”escape()编码就是“Hello%20World”。因为空格的Unicode值是20(十六进制)。

 

还有两个地方需要注意。

首先,无论网页的原始编码是什么,一旦被Javascript编码,就都变为unicode字符。也就是说,Javascipt函数的输入和输出,默认都是Unicode字符。这一点对下面两个函数也适用。

 

其次,escape()不对“+”编码。但是我们知道,网页在提交表单的时候,如果有空格,则会被转化为+字符。服务器处理数据的时候,会把+号处理成空格。所以,使用的时候要小心。

七、Javascript函数:encodeURI()

encodeURI()Javascript中真正用来对URL编码的函数。

它着眼于对整个URL进行编码,因此除了常见的符号以外,对其他一些在网址中有特殊含义的符号“; / ? : @ & = + $ , #”,也不进行编码。编码后,它输出符号的utf-8形式,并且在每个字节前加上%

 

它对应的解码函数是decodeURI()

 

需要注意的是,它不对单引号'编码。

八、Javascript函数:encodeURIComponent()

最后一个Javascript编码函数是encodeURIComponent()。与encodeURI()的区别是,它用于对URL的组成部分进行个别编码,而不用于对整个URL进行编码。

因此,“; / ? : @ & = + $ , #”,这些在encodeURI()中不被编码的符号,在encodeURIComponent()中统统会被编码。至于具体的编码方法,两者是一样。

 

它对应的解码函数是decodeURIComponent()

 

参考文献:http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

六、tomcat容器的编码

   1、启动tomcat容器时的编码

    在第二节中,说道:慎用getBytes(),他是基于平台的,tomcatJVM启动的时候file.encoding这个属性值到底是什么,由谁决定?

       Linux修改catalina.sh文件

JAVA_OPTS=”-server -Dfile.encoding=GBK -Xms=512m -Xmx1024m -XX:PermSize=128m -XX:MaxPermSize=256m -verbose:gc -Xloggc:${CATALINA_HOME}/logs/gc.log`date +%Y-%m-%d-%H-%M` -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -noclassgc”

      

    如果你像上面这样告诉JVM,启动我tomcat容器的时候,使用GBK来编码,后续的getBytes返回的就是GBK编码的东西,这点特别在部署的tomcat的时候注意。

 

   2tomcat对表单中get提交的数据,出乱码看哪里?

    get提交的tomcat4.x之后不是使用request.setCharacterEncoding("字符集"),从这里获取,它直接使用直接对参数进行编码,这个编码他是从tomcat的配置文件中获取<Connector port="8080" protocol="HTTP/1.1" maxThreads="150" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/> 

   

   3tomcat对表单中post提交的数据,出乱码看哪里?

    对于post需要在web.xml里面配置拦截器,主要是在程序第一次从request里面获取参数之前把request给设定了具体的编码

    publicvoid doFilter(ServletRequest req, ServletResponse resp,FilterChain chain)throws IOException, ServletException {

           req.setCharacterEncoding(encoding);//这里是过滤器,直接设置编码

           HttpServletRequest request = (HttpServletRequest)req;

           HttpServletResponse response = (HttpServletResponse)resp;

           ActionContext.setContext(request, response);

          

           chain.doFilter(req,resp);

           ActionContext.removeContext();

    }

 还有一点需要注意的是:你在web.xml里面配置过滤器的优先级是不是最高,一定要在request出现之前拦截。

   4tomcatjsp页面的编码,看哪里?

    你还记得jsp页面中有下面这句话么?pageEncoding="utf-8"告诉tomcat,你编译我jsp文件的时候使用他来编译,如果这句话没有,他默认使用charset=utf-8"这句话来编译,同时注意保存jsp的时候使用编码也应该与pageEncoding="utf-8"保持一致

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>

 

   你还记得下面这句话么?由他来告诉浏览器使用什么编码的

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

七、mysql数据库的编码

1、查看数据库编码

需要以root用户身份登陆才可以查看数据库编码方式(root用户身份登陆的命令为:

>mysql -u root –p,之后两次输入root用户的密码),查看数据库的编码方式命令为:

 >show variables like 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

从以上信息可知数据库的编码为latin1,需要修改为gbk或者是utf8

其中,character_set_client为客户端编码方式;character_set_connection为建立连接使用的编码;character_set_database数据库的编码;

character_set_results结果集的编码;

character_set_server数据库服务器的编码;

只要保证以上四个采用的编码方式一样,就不会出现乱码问题。

 

2、更改数据库编码

停止MySQL的运行
/etc/init.d/mysql start (stop)
为启动和停止服务器

MySQL主配置文件为my.cnf,一般目录为/etc/mysql

var/lib/mysql/ 放置的是数据库表文件夹,这里的mysql相当于windowsmysqldate文件夹

当我们需要修改MySQL数据库的默认编码时,需要编辑my.cnf文件进行编码修改,linux下修改mysql的配置文件my.cnf,文件位置默认/etc/my.cnf文件

找到客户端配置[client] 在下面添加
default-character-set=utf8
默认字符集为utf8
在找到[mysqld] 添加
default-character-set=utf8
默认字符集为utf8
init_connect='SET NAMES utf8'
(设定连接mysql数据库时使用utf8编码,以让mysql数据库为utf8运行)

 

3、改了之后,为什么win里面cmd中的那个console控制台还是显示乱码了?

         Mysql通过客户端发送到控制台展示之前是utf8,但是console是使用系统的默认编码(gbk),所以在中文的时候发生了乱码显示,但是不影响程序操作,只是显示的时候有问题。

         如何解决了?

         方法一:(只对当前窗口有用,推荐使用)

         输入:set names gbk

 

 

         方法二:将控制台展示编码改成utf8(对所有的窗口,不推荐)

1、打开CMD.exe命令行窗口 
2
、通过 chcp命令改变代码页,UTF-8的代码页为65001 
F:\trash>chcp 65001
执行该操作后,代码页就被变成UTF-8了。但是,在窗口中仍旧不能正确显示UTF-8字符。 

3
、修改窗口属性,改变字体 
在命令行标题栏上点击右键,选择"属性"->"字体",将字体修改为True Type字体"Lucida Console",然后点击确定将属性应用到当前窗口。 
4
、通过以上操作并不能完全解决问题,因为显示出来的内容有可能不完全。可以先最小化,然后最大化命令行窗口,文件的内容就完整的显示出来了。

 

分享到:
评论
1 楼 hautbbs 2013-02-22  
难得一见的好文章!

相关推荐

Global site tag (gtag.js) - Google Analytics