编码的一些理解
已迁往 http://fatmind.iteye.com
主要参考:
1.宝宝的文章《中文化和国际化问题浅析》
2.阮一峰的网络日志
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
http://www.ruanyifeng.com/blog/2010/02/url_encoding.html
一、编码基础知识
1. 为什么要编码、解码
计算机存储的最小单位是“位bit”,存储的所有内容都是一串二进制的表示。但最小的存储单元是byte,内存的编址也是以字节为单位,大部分的计算机系统都是以字节为单位进行存储、计算、传输的。导致:字符(多字节表示)与字节的相互转换。
概念:编码:字符转换为字节;解码:字节转化为字符。
2. 常见基本字符集
字符集的概念:在确定的规则下,一个value与一个字符的对应关系。
ASCII :只使用了一个8位字节中的低7位,总共是128个编码位。(最早由美国制定,定义英文字符与二进制位之间的关系)
Iso8859-1:单字节编码,8位全部使用,涵盖了大多数的西欧文字。(Unicode的最开头256个字符编码和ISO-8859-1是一一对应)
gb2312、gbk、gb18030 : gb2312(2字节)是依据iso的标准制定、gbk(2字节)是国家技术监督局1995年为中文Windows 95所制定的新的汉字内码规范、gb18030全称是《信息交换用汉字编码字符集》,是我国的强制标准(包含日韩等,支持单字节、双字节、4字节编码,中国大陆的标准)
ISO制定了ISO 2022标准,提供了七位与八位编码字符集的扩充方法的标准,gb2312是根据ISO 2022标准制定。
多字节编码,只是对中文及相关字符进行编码,可以认为是ascii的扩充(问题:如何区分ascii码与gb?)。gb2312规定:2字节编码,只使用每个字节的低7位,高位为0。结果:在计算机内部无法区分ascii码与gb码,出现“机内码(计算机内部表示汉字的编码),gb每个字节最高为置为 1 ” 。字符集依次增长的过程,向下兼容。
为什么存在区位码?不理解,请大家指点。
Unicode :UTF为UCS Transformation Format的缩写(深入了解:参考UCS的结构)。Unicode只是一个符号集,它只规定了符号的二进制代码,每一个符号都给予一个独一无二的编码,却没有规定这个二进制代码应该如何存储。
问题:汉字“严”的unicode是十六进制数4E25,两个字节。如何才能区别unicode和ascii,计算机怎么知道2个字节表示一个符号,而不是分别表示2个符号呢?unicode没有规定具体的编码方式。
常用的unicode实现方式有UTF-8和UTF-16。容纳全世界的编码,优点:中文的网页,在英文版的浏览器也能正常显示,不会乱码。
UTF-16:定长双字节
缺点:数据量增大,如果对于英文字符,增加2倍;属于ascii字符,高字节填补为0x00,在C里面是字符串的结尾,带来无数问题。
优点:直接表现字符编码的整数值,所以UTF-16是最直接的Unicode表示法。它是定长的,这大大简化了字符串的操作,Java语言就是用UTF-16格式将字符存储在内存中的。
UTF-8:变长字节编码(1-6字节)
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩余的二进制位,全部为这个符号的unicode码。
简单结论:
1. 如果一个字节,最高位(第8位)为0,表示这是一个ASCII字符(00 - 7F )。可见,所有ASCII编码已经是UTF-8了。
2. 如果一个字节,以11开头,连续的1的个数暗示这个字符的字节数,例如:110xxxxx代表它是双字节UTF-8字符的首字节。
3. 如果一个字节,以10开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节。
4. ascii单字节编码,中文属于3字节范围。
3. 为什么乱码
乱码有很多种情形,但是万变不离其宗,就是“解码所用的charset和编码所用的charset不兼容”。
Unicode规定,编码时,碰到“看不懂”的字符,一律用“?(0x 3F )”表示。
Unicode规定,解码时,发现“看不懂”的字节,一律用“�(0xFFFD)”表示。
4. 引用“宝宝总结”
1. 字符编码是抽象字符在计算机中的数字表示。
2. 字符编码集(character set,简称字符集)是一批字符编码的集合。世界上存在大量互不兼容的字符集,给国际交流带来了困难。
3. ASCII码是最古老的字符编码,它总共只定义了7位共128个字母、数字和符号。但它是其它所有字符编码的基础。
4. Unicode常用UTF-8和UTF-16来表示。7位的ASCII码不用作任何变化,就已经是UTF-8了。但UTF-8需要用3个字节来表示一个汉字。
5. ISO 8859系列字符集,定义了单字节字符编码的标准。其中最特殊的是ISO-8859-1编码,它的编码和Unicode中最开始的256个字符编码完全相同。
6. GB18030编码是中国大陆的国家标准,在字汇上等同于Unicode,在编码上和GB2312编码以及GBK编码兼容。
二、Java编码问题
宝宝:分析Java中文乱码问题的根本原因,首先要了解这些中文字符的输入源,其次是了解这些字符被输出到用户浏览器经过了哪些转换和输出环节。
中文字符可以来源于:
1. 程序内嵌的中文,源代码里直接写中文字符串。
2. 外部读入的中文。
1. 程序内嵌中文:
因为Java源代码(.java)本身是一个文本文件,所以和读普通文本文件一样,编译器(javac)必须以字节流的方式读入文件内容,并以适当的编码转换为Unicode字符存储在Java字节码文件(.class)中。例如 :Java源代码文件中包含GBK编码的中文字符,则使用下面的命令编译:
javac -encoding GBK MyClass.java
如果不指定-encoding参数,javac会使用JVM默认的编码(Java虚拟机取操作系统编码) 在中文Windows上,默认是GB18030,在英文Linux上,默认是ISO-8859-1。因此,如果文件是在英文Linux下编译而未指定-encoding,那么文件中的中文“我爱Alibaba”就会变成“ÎÒ°®Alibaba” 。
Jsp :<%@page contentType="text/html; charset=GBK"%> //决定读入文件编码和输出数据流编码
2. 外部读入的中文 :
比如:数据库读入、文件读入、用户数据的提交
用户数据的提交:url串、post
一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号。这是因为网络标准RFC 1738做了硬性规定:
只有字母和数字[0 -9a -zA-Z]、一些特殊符号“$-_.+!*'(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL
这意味着,如果URL中有汉字,就必须编码后使用。但是麻烦的是,RFC 1738没有规定具体的编码方法,而是交给应用程序(浏览器)自己决定。这导致“URL编码”成为了一个混乱的领域。
一些结论:
1. 网址路径的编码,用的是utf-8编码. (firefor&IE相同)
http://www.taobao.com/春节 %E6%98%A5%E8% 8A %82
2. 直接在浏览器输入查询字符的编码,不同的浏览器有不同的处理.
http://www.taobao.com/?q=春节
chrome : %E6%98%A5%E8% 8A %82
firefor(中文版): %B4%BA%BD%DA
3. GET和POST方法提交的编码,由网页的编码决定.
Baidu / google 搜索“春节”,url串的结果
4. Ajax调用的URL包含汉字, IE总是采用GB2312编码(操作系统的默认编码),而Firefox总是采用utf-8编码. (未验证)
3. 数据在内存中的编码
Java用unicode(实现unicode-16)编码,Java能够实现unicode与其它字符集之间的转换。比如: GBK的字节串d6 d0转换成utf8字节串e4 b8 ad的过程是:GBK bytes (解码) -> unicode (编码) -> UTF8 bytes(解码),而不是gbk -> utf-8。
4. 数据输出
4.1 Servlet输出
字符流方式 response.getWriter()。用来输出文本类型的内容,如HTML和纯文本。 在调用response.getWriter()前,我们必须设置content type :response.setContentType("text/html; charset=GBK");
response.getWriter()通过content type中指定的字符编码来决定如何将字符流转换成字节流。
4.2 浏览器如何确定页面的字符编码
浏览器收到从WEB服务器返回的页面时
1.首先检查HTTP响应中指定的contentType,也就是servlet通过response.setContentType方法设置的值。如果content type中指定字符编码(例如text/html; charset=GBK),则使用这种方式解码这个页面。
2.如果HTTP响应中没有指定字符集,那么浏览器会检查HTML页面中是否包含:
<meta http-equiv="Content-Type" content="text/html; charset=GBK">如果找到,则使用这里指定的字符编码
3.如果既没有在HTTP响应中指定字符编码,也没有在HTML内容中指定字符编码,则浏览器根据一定的规则自动确定页面的字符编码(字符集探测)。例如,在英文环境中,浏览器会使用ISO-8859-1,简体中文环境中,则使用GBK。
三、总结:
分析Java中文乱码问题的根本原因,首先要了解这些中文字符的输入源,其次是了解这些字符被输出到用户浏览器经过了哪些转换和输出环节。
乱码有很多种情形,但是万变不离其宗,就是:解码所用的 charset 和编码所用的 charset 不兼容 。