首页 >> 编程知识

学艺要精,思考要慎.--转UTF-8编码的启发 、浅析搜索网站应注意的编码问题

学艺要精,思考要慎.--转UTF-8编码的启发



学艺要精,思考要慎.--转UTF-8编码的启发 


 本次对UUZone 
Refactor中一个大动作就是采用UTF-8编码,由于种种的历史原因,uuzone一直采用GBK编码,现在要把这个编码转成为UTF-8的。 


本文不打算讨论技术, 所以简单地说明这个工作进展的一个过程: 


** 最早的时候*,n年前,我们一个项目需要国际化支持,毫无疑问utf-8是最佳的编码选择,这时候项目组就遇到了问 题: 
本地编写的文件中的中文都是GBK编码的, 仅仅设置页面的contentType="text/html; charset=UTF-8", 
出来的全是乱码...当然拿GBK的东西直接要求对方用UTF-8看,一定是乱码. 


   当时如何解决的过程已经无法了解, 但最终采用的方案是:  用GBK写文件, 然后采用JDK中的native2ascii工具转换. 问题解决了, 
但结果就是多了转码的过程, 转码后的jsp文件人不可读. 


** 做uuzone了*,  项目组建议采用GBK编码, 因为那个转码工作太繁琐了,而且导致文件不可读, 调试非常麻烦. 
于是uuzone就成了GBK的. 


   由于采用GBK, 遇到了很多相关的问题... 中途至少有2次我"建议"改用UTF-8, 
都被评估认为有太多问题而没有被采纳(郁闷的boss啊...)... 


** 发现原来不需要转码!*  终于处于某些原因, 我需要自己动手来研究一些技术细节, 
发现native2ascii转码<http://www.uuzone.com/blog/tom/98879.htm>的工作其实并不需要进行. 
jsp文件可以直接保存为UTF-8编码, 输出的结果就是UTF-8的...而现在的elips,editplus, ultraedit, 
甚至notepad都能保存和识别utf-8的文件. 


** 终于下定决心转为UTF-8.* 既然如此, 就没有理由拒绝了. 因此本次refactor项目目标之一就是改用UTF-8. 
既然文件打算用utf-8保存, 那么就需要批量转换工具...几下search, 好的工具也找到了. 


** 转!*   有工具, 不到20秒, 6000多个文件就从GBK转成了UTF-8文件格式... 


** 问题来了!* 项目中的XML, properties文件是否转码呢? 本来计划转...但很快发现,这些文件转掉后, 
XML,properties文件的读取都出了不同程度的问题...总之项目将根本无法运行... 
原因何在?原来这些UTF-8的文件有一个所谓BOM<http://www.unicode.org.unicode/faq/utf_bom.html>头,也就是告诉你这个文件的编码是什么...不幸的是Java的xml 
parser, properties文件parser都不认识这个BOM头<http://www.uuzone.com/blog/tom/98677.htm> 
!已经有人把这个作为Bug提交给了SUN并被接受<http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058>, 
可是这个bug一直拖了4年,才在去年在Mustang项目(Java 
6.0)中解决<http://glaforge.free.fr/weblog/index.php?itemid=151&catid=2>! 
我们显然不能用了. 


** 幸运的是... *我们可以规避这些问题, 我们并不需要把XML, properties文件转换成UTF-8的, 只要把jsp转了即可. 
虽然一个项目中的文件两种保存格式,非常不优雅, 但毕竟这能解决问题啊... 转换完jsp文件,运行项目, 成功! 打开browser...页面也出来了! 


** 几乎成功了!* 是的, 几乎成功了...然而在计算机的世界里几乎成功就是不成功. 因为页面上的样式神秘地出了轻微的混乱. 
经过n十分钟仔细研究讨论, 终于发现产生的html页面内出现了几个神秘的不可见字符... 就是这些可怕的不可见字符, 破坏了页面的结构. 


** 神秘的隐身字符*  和计算机打交道和比人打交道强的一个地方就是在于 -- 如果计算机错了,一定是你错了, 因为计算机一定忠实执行你的指令. 
神秘的不可见字符一定有其来历. 经过二进制分析, 找到了这些神秘字符出现的地方 ---jsp include! 每次include就会出现神秘字符, 
而这些神秘字符的值也是老朋友了 -- 就是BOM header ! 


** 难道不能include了?*  原来java显然和xml文件一样对待了这些BOM头, 这些FF FE之类的东西成了输出的一部分. google 
去查, 看到一个人显然遇到完全相同的问题, 可惜是个俄罗斯的程序员, 也看不懂其文章... :( 


    怎么办?  有同学说: 既然你要UTF-8 (心想: 谁让你是boss呢? 愚蠢的boss啊...:-) ) , 
我们还是用native2ascii吧...  不行! 


** 遇到困难要思考.* 思考... 


    首先, 为什么java文件不需要转码(java 文件中的中文无论如何都能正确)? 为什么jsp要? jsp不是被预编译成java的吗? 


    从那个sun承认的bug, 显然可以得出一个结论, java虚拟机中的file input stream没有能自动很具BOM识别文件编码. 


    也许javac是比较native的代码, 能通过操作系统获得native编码类型, 而java内部是unicode的, 所以输出也同样没问题. 
而jsp是动态编译的, 预编译器是JVM...其待遇显然和xml等是一样的... 


   难道sun公司的人会愚蠢到这种地步? 不可能, 一定是我们愚蠢.继续google 学习... 


    不幸的是大部分google上关于这类编码问题的讨论都是不完整的. 但一个神秘的pageEncoding跳进了眼帘, 
已经指定了contentType的encoding, 为什么还要指定pageEncoding?  查一下jsp的资料, 显然这正是问题所在! 
pageEncoding就是告诉JVM 这个jsp本身采用的encoding, 默认采用iso-8859. 我们明明用GBK的native编码, 
让JVM拿iso8859读取,当然不对了!! 


** 根本没有需要转码, 问题解决了!* 是的, 明白了原因所在, 把所有jsp文件头修改为: <%@ page language="java" 
contentType="text/html; charset=UTF-8" pageEncoding="GBK" %> 
不需要任何转码,最终输出的就是UTF-8, 没有任何问题. 而且这些文件采用native格式保存, 编辑最方便! 


------------------------------ 
繁琐写了一大堆,说明了过程. 其中的教训是深刻的, 这就是我的标题中的: 


 学艺要精,思考要慎 


其实,在这个问题上折腾这么久, 只能怪自己学艺不精. pageEncoding是任何jsp的手册上都会说明的, 然而都被忽视了. 这个问题 
我写这么多,让高人(甚至不需要高人, 只是认真读书看手册的人)见了, 只会见笑 --你们太烂了. 不过从google上看, 和我们一样烂的人太多 太多, 
还有很多人在混乱编码的泥潭中挣扎. 


拉起来就做,还没搞明白怎么回事,甚至"硬编码"解决,害人最深. 


不幸的是,遇到了太多的项目, 太多的人, 他们宁可copy paste大量代码,花很多时间去debug, 费九牛二虎之力,把boss在心中诅咒100遍, 
用不正确的方法去做事, 最终的结果可想而知. 


以此为记, 希望今后我们项目中能不重复这样的错误, 也希望我们每个人,能多思考,用聪明而省力的方法去解决问题,而不是蛮干.



浅析搜索网站应注意的编码问题



互联网让世界变得越来越小,大多数WEB站点不得不考虑适应各种编码的问题。本文结合Google的做法,简单谈谈如何让站点有效支持多种语言,暂且先不谈各种编码的背景知识。

    通常一个提供搜索服务的网站涉及编码主要在以下几点:

一、界面语言显示

    中文用户浏览google.com通常会直接显现中文界面。Google如何实现的呢?看一下浏览器发出的请求:

GET / HTTP/1.1

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/msword, */*

Accept-Language: zh-cn

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)

Host: www.google.com

Connection: Keep-Alive


HTTP1.1协议规定了 Accept-Language节,可以使支持多语言的服务器根据此项返回正确的结果。如果没有指定 Accept-Language呢?telnet连上去试试看:



[fancao@free:~] $ telnet www.google.com 80

Trying 64.233.189.104...

Connected to www-china.l.google.com.

Escape character is '^]'.

GET / HTTP/1.1



HTTP/1.1 302 Found

Location: http://www.google.com/intl/zh-CN/

Cache-Control: private

Set-Cookie: PREF=ID=01710c01d6f2632d:NW=1:TM=1144067151:LM=1144067151:S=r3D24oIQQIa9u6I_; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com

Content-Type: text/html

Server: GWS/2.1

Content-Length: 230

Date: Mon, 03 Apr 2006 12:25:51 GMT



<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">

<TITLE>302 Moved</TITLE></HEAD><BODY>

<H1>302 Moved</H1>

The document has moved

<A HREF="http://www.google.com/intl/zh-CN/">here</A>.

</BODY></HTML>


       Google还是导向到了中文站点 http://www.google.com/intl/zh-CN/。可以看到实际请求是连接到了 www-china.l.google.com。

         再通过浏览器测试一下,这次去掉浏览器中语言设置,也就是不指定Accept-Language,分别浏览www.google.com.cn、www.google.com.tw、www.google.co.jp、www.google.co.kr,可以看到不同语言的结果。也就是说不同的服务器提供的默认界面语言是不一样的。

       如果指定了Accept-Language=zh,然后再次浏览http://www.google.co.kr/,这次看到的结果就不是韩文了,而是简体中文内容。可见对Accept-Language的判断优先级是要高于服务器默认语言的。

        Google可以让用户设定界面语言,设定到哪里了?再来看看设定了简体中文界面后的浏览器请求(Cookie部分):

       Cookie: PREF=ID=6f389883f4bc8b9b:LR=lang_ja|lang_zh-CN|lang_zh-TW:LD=zh-CN:NR=10:NW=1:TM=1144071286:LM=1144071338:S=91yUwz0pwGg8eb0W

       原来是在客户端种植了Cookie,这样不管IP在何方,也不管 Accept-Language是什么,都可以按照用户的设置来显示。

       参照Google的做法,总结为以下3点:

       ·根据用户设定种Cookie,读Cookie

       ·没有Cookie时,判断http请求中的Accept-Language

     ·部署不同默认语言的服务器,根据IP地址解析到本地区语言的服务器

      

二、输入内容的编码识别

       编码的识别实际是很困难的,浏览器对网页的识别主要依据两点:

       ·HTTP协议响应头中的charset

       ·HTML页面中的charset项

       HTTP协议头中charset优先级要高于HTML文件中的charset,可以做个小小测试:

<?php

        header("Content-Type: text/html; charset=iso-8859-1");

?>

<html>

       <head>

              <meta HTTP-EQUIV="content-type" CONTENT="text/html; charset=GB2312">

       </head>

       
              汉字文本内容

       </body>

</html>


    上面的php程序无法正确显示汉字,是因为在HTTP头中指定了 charset=iso-8859-1

    如果两项都没指定呢?浏览器也会比较难办。通常识别编码的方法是根据字节码去猜,看放在那个编码区中解释更合理。

    那么一个搜索站点如何判断用户输入字符的编码呢?再来做测试,

在一个UTF-8页面的FORM中提交了“中国”,会被解释为:

http://localhost/test.php?test=%E4%B8%AD%E5%9B%BD

在一个GB2312页面的FORM中提交了“中国”,会被解释为:

http://localhost/test.php?test=%D6%D0%B9%FA

        %E4%B8%AD%E5%9B%BD 是“中国”两字的UTF-8编码做了urlencode之后的结果,而%D6%D0%B9%FA则是“中国”两字的GB2312编码做了urlencode之后的结果。这样我们就很清楚了,浏览器会根据当前页面语言GET或者POST数据。

    这样的话,只要页面编码为UTF-8,然后按照UTF-8来解析查询数据就可以解决了。

    再来看看Google是怎么玩的。Google页面的编码为UTF-8,在Google中搜索“中国”,看到的URL为:

http://www.google.com/search?sourceid=navclient&hl=zh-CN&ie=UTF-8&rls=AMSA,AMSA:2006-11,AMSA:zh-CN&q=%E4%B8%AD%E5%9B%BD

好面熟的%E4%B8%AD%E5%9B%BD。

    上面的URL中有ie=UTF-8一项,分析了一下发现ie就是input encode。这次难为一下Google,不指定input encode,再分别按照UTF-8和GB2312编码查询一下Google:

http://www.google.com/search?q=%E4%B8%AD%E5%9B%BD

http://www.google.com/search?q=%D6%D0%B9%FA

    Google是在默认语言编码和UTF-8两种编码直接做了识别。如何做到的呢?UTF-8编码有一定的规则,可以按照此规则做个判断,Google正是如此判断的。但这种判断有时会出错,例如:

http://www.google.com/search?q=%D1%A7%CF%B0

%D1%A7%CF%B0是“学习”两字的GB2312编码做了urlencode。错误的原因是误把“学习”的GB2312编码当作了UTF-8。

    参照Google的做法,可以提出如下方案:

    ·统一用UTF-8编码:页面用UTF-8编码并且按照UTF-8编码解析查询数据

     ·复杂一些,尝试按照UTF-8规则分析查询数据,如果不符合规则,就认为是当前语言编码


三:更有效的搜索结果

        用户总是希望得到更多的搜索结果。中文网页内容有GBK、Big5、UTF-8等编码,日韩各种编码中还有一部分汉字。如何才能让用户的搜索更有效的涵盖这些网页呢?这次不拿Google做测试了。根据编码的背景知识,提出如下解决方案:

·简繁转换

       简繁转换主要是将用户搜索请求按照两种方式来搜索。无论用户输入“大眾”还是“大众”,都按照这两个请求同时来搜索。   

·原始网页转为UTF-8编码做索引

       原始网页转为UTF-8来做索引有很大的便利,这样有效地将各种编码统一来处理。

学艺要精,思考要慎.--转UTF-8编码的启发 、浅析搜索网站应注意的编码问题(本文完毕)
下一篇:爱情函数
上一篇:中文网站应选用何种编码