1. ASCII码
我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从0000000到11111111。
上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。
ASCII码一共规定了128个字符的编码,比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。
2、非ASCII编码
英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。
但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0—127表示的符号是一样的,不一样的只是128—255的这一段。
至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256×256=65536个符号。
中文编码的问题需要专文讨论,这篇笔记不涉及。这里只指出,虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。
3.Unicode
正如上一节所说,世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。
可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。
Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字“严”。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。
4. Unicode的问题
需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
比如,汉字“严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。
这里就有两个严重的问题,第一个问题是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。
它们造成的结果是:1)出现了unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示unicode。2)unicode在很长一段时间内无法推广,直到互联网的出现。
5.UTF-8
互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用编码的位。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
——————–+———————————————
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
下面,还是以汉字“严”为例,演示如何实现UTF-8编码。
已知“严”的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此“严”的UTF-8编码需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。
6. Unicode与UTF-8之间的转换
通过上一节的例子,可以看到“严”的Unicode码是4E25,UTF-8编码是E4B8A5,两者是不一样的。它们之间的转换可以通过程序实现。
在Windows平台下,有一个最简单的转化方法,就是使用内置的记事本小程序Notepad.exe。打开文件后,点击“文件”菜单中的“另存为”命令,会跳出一个对话框,在最底部有一个“编码”的下拉条。
先解释下,什么是地址栏用中文参数。地址栏用中文参数的更确切的说法应该是,GET请求中参数直接使用中文字符串,而不做任何URLEncode。
举个例子,在www.google.com中搜索“我”,请求是用的GET方式发送的,页面打开后,地址栏(用的Safari,苹果上的网页浏览器)显示的是“http://www.google.com/search?client=safari&rls=zh-cn&q=我&ie=UTF-8&oe=UTF-8”,这就是一个GET请求参数直接用中文字符串的例子。用IE的同志注意了,上面的链接点击后到达的页面是不正常的,显示的不是搜索“我”的页面,把这个链接复制了,然后粘贴到IE的地址栏里,访问到的页面也是不正常的,问题是这次的不正常和刚才的不正常还不一样。这个例子告诉我们,地址栏用中文参数还真的有可能是“不可能的任务”。
我先分析下问题产生的原因,然后再谈一谈解决这个问题的最佳实践。
原因分析:
如果我们的用户都用的是一种浏览器,那么问题要简单的多,只要弄明白这个浏览器到底是怎么表现的,针对这个做一些编码的转换就行了,但是问题就在于,我们的用户用了至少是IE,Firefox和Safari这三种浏览器(注:奥游和腾讯TT本质都是IE),而这三个浏览器的表现很不一样。
1.首先说Firefox,在Firefox的地址栏中用中文参数,它的表现是用UTF-8编码进行URLEncode,并且把encode之后得到的字符串显示在地址栏。
2.然后是Safari,Safari和Firefox基本一致,也是用UTF-8编码进行URLEncode,但是Safari会自作聪明一点,如果参数里有它认识的URLEncode后的字符串,如“%E6%88%91”(注:这段是“我”经过URLEncode得到的),什么是Safari认识的呢,就是可以用UTF-8编码进行URLEncode得到的,它就会恢复原始中文的显示。所以开篇safari中得到的地址栏链接就是含中文的。
3.最后是IE,IE表现的最无语,上面大家都看到了,同样的链接,在页面中打开和在地址栏输入得到的都不一样。这是怎么造成的呢?IE在处理中文参数的时候,对于直接输入在地址栏里的,使用的是系统平台对应的编码,比如我们用的Windows中文版,那就是用的GB18030编码,而对于页面链接里的,使用的是页面的编码,比如这篇文章的页面编码是UTF-8,所以从这个页面打开上面的链接,中文是用UTF-8编码进行URLEncode的,而在地址栏,直接输入,使用的是GB18030的编码。处理的不同也就罢了,但是不能在地址栏里一点区别也看不出来阿,所以IE是表现的最无语的。
那么,如何解决这个问题呢?处理过浏览器兼容问题的同志应该会想到对不同浏览器区别对待,这个想法是可行的,因为用户用的什么浏览器在服务端是可以获得的。但是IE使这个问题复杂了许多,我们并不知道用户是点击的页面中的链接,还是在地址栏里直接输入的,而且我们也未必知道页面的编码和系统的语言环境。
看起来,我们只能根据原始内容“动态识别字符编码”了。其实“动态识别字符编码”对于我们来说并不陌生,我们的浏览器都是支持我们自己指定用什么编码来打开页面的,用的最多的情况是Web邮箱,有的邮件我们可能要换一个编码才能看,否则全是乱码,网页的编码在没有指定的情况下,浏览器会猜一个编码,这就是“动态识别字符编码”。另外,有些文本编辑器也是支持多种文件编码的,打开一个文件时,也会“动态识别字符编码”。
如何实现“动态识别字符编码”呢?cpdetector是一个开源项目,这个项目的主页是http://cpdetector.sourceforge.net/,这个项目提供了“动态识别字符编码”我已经适用,基本是可以识别的。
这里还需要解释一个服务器端用Java相关技术时的问题,Tomcat默认的URIEncodeing用的是ISO-8859-1编码,这造成的是在Firefox或Safari这样的不是很无语的浏览器上,对于中文数据,POST请求和GET请求也表现的不一样,如果页面用UTF-8编码,GET时是乱码的。这事实上是另一个“地址栏用中文参数”的问题,这个问题我们一般搜索到的解决办法是修改tomcat的URIEncodeing参数为UTF-8。
当然我们不是一定要做这样一个操作,我们既然知道数据是用ISO-8859-1编码的,我们大可getBytes("ISO-8859-1")。但是我这样做,然后配合用cpdetector“动态识别字符编码”的方案,又发现了问题,有时候IE下对一些字符串做了一些编码转换操作之后就出现问题了,我试出来的例子是,在编码是UTF-8的页面上提供了一个包含参数为“我是谁”的链接,点击后,经过了getBytes("ISO-8859-1")和cpdetector“动态识别字符编码”,得到了我是口(注:口是个表示不能识别的框框),有些参数直接识别字符编码都失败,比如“你我他”被识别成了“x-EUC-TW”。这些问题基本是不可解决的。我也尝试了修改tomcat的URIEncodeing参数为UTF-8,问题依然,而且cpdetector居然也识别不出来直接输入地址栏时的编码GB18030。这终于是个名符其实的不可能的任务了。
最佳实践:
由于无语的IE,我们放弃了地址栏用中文参数,我们乖乖的使用URLEncode,但是URLEncode就可以了吗?答案是否定的,要知道,自作聪明的Safari会吧URLEncode后的字符串还原称原来的中文,那么Safari的地址栏里常常是有中文的,那么一个Safari用户把这个链接发给一个IE用户,后者不又Shit了吗?所以我们要对中文字符串进行两次URLEncode,由于服务器会自动进行一次URLDecode,所以我们在服务端编程再进行一次URLDecode。这就是最佳实践,放弃了友好的URL,放弃了简洁和清晰,技巧近乎变态,但这是唯一的能够充分避免无效链接的方法。校园网络套餐中课程的文件柜中的中文参数就采取了这种方法。
总结:
本篇基本实践了“地址栏用中文参数”的努力中的各种问题,实践证明“地址栏用中文参数”是不可能的任务,然后以一种极度为用户负责的态度提出了一个有点变态的最佳时间。
最后忍不住说一句:IE给程序开发造成的不便,绝对不仅仅是这个,下次请个做前端开发的同志谈一谈。
地址栏中出现汉字的情况有两种,一种是汉字出现在URL的路径部分,一种是汉字出现在URL的传参的部分,第二种情况的时候必须采用编码后传参,接受时解码的方式完成传参。js中编码有escape(),encodeURI(),encodeURIComponent()三个常用的方法,escape()常常用在提交页面和处理页面的编码格式相同的情况下(比如它们都是GB2312),encodeURI()和encodeURIComponent()的用法基本相同,区别在encodeURIComponent()也对"?"等特殊字符进行编码。
一开始遇到中文参数的时候,使用encodeURI()进行了一遍编码,传过去后,发现解码出现问题,于是想到可能是编码方法使用错误,于是使用escape()方法,这时发现解码时抛出isHexDigit异常。借助百度搜索isHexDigit异常,发现原来,是escape()方法造成了异常,同时了解了浏览器传递地址的一些原理,在浏览器地址栏里,浏览器认为%是个转义字符,浏览器会把%与%之间的编码,两位两位取出后进行解码,然后再传递给处理页面,然后由处理页面进行再次解码。由此我想到一直使用encodeURI方法是正确的,只是需要使用两次encodeURI方法,例如encodeURI(encodeURI("中文"));第一次是把中文编码成%xy的格式,第二次是对%xy中的%进行编码,%编码成%。整个传参过程大体应该是:提交页面使用encodeURI(encodeURI("中文"))编码,把最后的编码结果%xy传递给处理页面的过程中,浏览器获取URL地址 (注意openModelDialog方法,浏览器获取不到参数编码)后解码成%xy,然后把%xy传递给处理页面,处理页面使用URLDecoder.decode(request.getParameter("参数名"),"UTF-8");完成解码。
总结:
1、汉字出现在URL路径部分的时候不需要编码解码;
2、使用encodeURI进行2次编码;
3、在openModelDialog()打开的模式窗体里没办法用request.getParameter正确获取参数;
客户端和服务器在传递数据时可以用过滤器filter解决字符编码问题,但filter只能解决post方式提交的数据。对于get方式,可以使用两次encodeURI(encodeURI(“中文”))并在服务器中使用URLDecoder.decode(“中文”, "UTF-8");
今天用Ajax校验数据时也遇到这个问题,尽管页面、类和web容器都统一了字符编码,提交的数据依然是乱码,所以就采用了2次encodeURI()编码方式,乱码问题就解决了。
页面
/exportExcel.topinfo?ls="+encodeURI(encodeURI(_tmplsgx))+"&zt="+encodeURI(encodeURI(_tmpzt))saction中
String ls=request.getParameter("ls");
ls = new String(ls.getBytes("iso-8859-1"),"utf-8");
ls = java.net.URLDecoder.decode(ls,"UTF-8"); 这样乱码就解决了。
在IE地址栏中经常会出现许多奇怪的编码,请问这些是什么编码?在何处有方面的资料?
http://www.google.com/search?q=%E5%B1%80%E5%9F%9F%E7%BD%91%E5%86%85%2B%E5%85%B1%E4%BA%AB%E5%85%A5%E4%BE%B5&hl=zh-CN&lr=lang_zh-CN&ie=UTF-8&oe=UTF-8&start=10&sa=N
学的太多了
那是编码后的中文字符或特殊字符。
网上哪有这方面的学习资料?
这些是把你的数据输入之后,再编译后的特殊符号
这也想学呀?编译技术
有无对照的转换表
这是网页的自动生成的编码。 如果你想知道的话。得先看看它是用什么语言编成的
search是网站上的的处理程序的入口,可以是cgi、php、asp等编程的程序,常见的如search.asp?XXXXX道理一样,?后边跟的是参数,%E5%B1%80%E5%9F%9F%E7%BD%91%E5%86%85%2B%E5%85%B1%E4%BA%AB%E5%85%A5%E4%BE%B5是Unicode编码编码的字符,&后边连结的是其他参数。
工具÷选项÷高级,最下面有个总是以 UTF-8 发送 URL,下面详述:
由 Unicode 协会开发的能表示几乎世界上所有书写语言的字符编码标准。Unicode 字符清单有多种代表形式,包括 UTF-8、UTF-16 和 UTF-32。大多 Windows 界面使用 UTF-16。
Unicode 传输格式 8 (UTF-8)
在 ASCII 码基础上发展的协议字符设置。UTF-8 协议提供 ASCII 码的扩展支持,UCS-2 码的翻译,国际 16 进制 Unicode 编码字符设置。与使用字符数据的 ASCII 或扩展 ASCII 编码相比,UTF-8 可获得更大范围的名称。
规范中建议,用户端在这种情况下应采取以下方式处理非 ASCII 字符: 1. 将每个字符转换为 UTF-8 编码的相同字符,每个字符将有一或多个字节。 2. 用 URI 编码机制对这些字节进行编码。如:每个字节转换为 %HH,其中的 HH 为这些字节的值的十六进制表示。这种方式称为“百分号编码”。 关于 URI 类型及 URI 属性值中的非 ASCII 字符(Non-ASCII characters in URI attribute values)的详细信息,请参考 HTML4.01 规范 6.4 URIs 及 附录B.2.1 中的内容。 关于“百分号编码”的详细信息,请参考 RFC-3986 2.1. Percent-Encoding 中的内容。 关于 URI 组成的更多信息,请参考 RFC-3986 3. Syntax Components 中的内容。 问题描述 对于 URI 中非 ASCII 字符,并非所有浏览器都是按照 HTML 4.01 规范中的建议实现的,而且不同浏览器在处理不同形式的 URI 时,表现也有差异。 造成的影响 这个问题将导致在服务端或客户端通过代码获取 URI 中的非 ASCII 字符信息时无法分辨编码信息,并产生乱码。