字符编码再进阶
sshong 发表于2008年4月6日 21:02:00 更新于2008年6月28日 14:11:00
  从最开始单字节字符编码ASCII---〉ISO8859-1(兼容ASCII更全的单字节字符编码,每一个字节直接作为一个 UNICODE 字符)--->ANSI编码(如GBKGB10830,多(不定)字节字符编码)等--->UNICODE(万国码,现为UCS-2即双字节编码)--->为便于传输UNICODE编码(UTF-8编码,将UNICODE编码按照某种转换规则进行算法压缩或改变便于网络传输)。

  最开始的操作系统英文DOS(ASCII)--->中文dos、中文win95/98、日文win95/98(ANSI)---〉win2000/xp/NT、Linux、JAVA(UNICODE)

  区分字节与字符。一个字符如'中'  根据不同的编码方案能转换为不同个数以及不同内容的字节。譬如用gbk编码,“中”对应两个字节“D6 D0”,而对UNICODE编码,对应两个字节"u4E2D"。
  
  c语言中字节用char表示,字符用wchat_t表示,而在windows中define为两个宏,CHAR和WCHAR,ANSI字符串(也就是字节串)用char[]表示,UNICODE字符串用wchar_t表示,在windows中对应的数据类型为LPCSTR,LPCWSTR,还有一种根据是否定义了UNICODE自动对应到前两种数据类型的LPCTSTR。

  java中字节用byte表示,字符用char表示,ANSI字符串(也就是字节串)用byte[]表示,UNICODE字符串用string表示。

  windows下的输入法--ime文件,需要实现相关的windows规定的函数。其大致流程如下。

       键盘事件  应用程序
       |    |
       Windows的USER.EXE
         |
       输入法管理器
         |
        输入法 

  输入法有支持不支持UNICODE之分,具体可以看这个函数
DWORD IMEConversionList( //将某字符或字符串转换成目标字符串
    HIMC hIMC, // 与当前输入法相关的应用程序句柄    
    LPCTSTR lpSrc,  //要转换的字符串  (也可能是结果串,可由uFlag指定)
    LPCANDIDATELIST lpDst,//转换后的字符串(也可能是源串,可由uFlag指定)
    DWORD dwBufLen, //转换后有几个字符
    UINT uFlag    //指定结果的存放位置
)
LPCTSTR估计就是根据是否定义了UNICODE宏来确定输出的是ANSI的还是UNICODE的编码字节流。

  获取这些字节流的用户程序也有是否支持UNICODE之分,不同的接收输入函数处理方式不同,应该是可以重载接受字节流的函数,譬如有两个函数,一个是读ANSI字节流,一个是读UNICODE字符流的,根据字节流的类型来进行UNICODE码转换并显示到界面中。如果是ANSI字节流,根据codepage来转换到UNICODE正确显示字符串,当系统中没有这个codepage时将会无法显示这些字符串。

  一个文本编辑器,假定按照上面的原理能有正确接收输入并显示字符,如果保存的时候,选择了ANSI编码,这些字符将转换为ANSI方式字节流保存,而如果你选择是UTF-8编码,这些字符将转换为UTF-8编码。那么,下次打开时,系统同样读取该字节流,并传递给文本编辑器界面框,界面框如何解析这些字节流呢?
  在windows中,如果你选择了非ANSI方式保存,  将在这个文件的头部添加两个字节的编码方式说明。见下图:

  因此,这也就解决了上述的问题。

  下面再来讲有关jsp以及html网页乱码部分。
  我们都知道jsp要转化为Servlet的.java文件的,然后由.java文件编译生成class文件的。
  因此我们如何保证这个过程中从jsp到Servlet,到class文件的字符编码呢?
  在JSP文件头部:<%@ page pageEncoding="gbk"%>,JSP/Servlet引擎提供的JSP转换工具(jspc)正是根据这个编码设置将jsp文件转换为UTF-8格式的.java文件的。只要保证转换为了正确地.utf-8格式的,后面编译为class文件同样就不会出错了。因此只要jsp文件保存时选择的编码与这个encoding一致,就可以保证最后的类文件有正确的编码。

  那么,下次tomcat获取这个类,输出的字节流又用到什么编码呢?既然最终输出给浏览器的都是一串字节流,那么浏览器如何知道怎么解析这个字节流呢?
   大家都知道,http有很多头,其中响应头(输出)有一个就是content-type,其中有一个charset,它告诉浏览器字节流用的是什么编码。

  对于jsp动态网页,指定文件输出输出到 browser所使用的编码,该设置也应该置于文件的开头。例如: <%@ page contentType="text/html; charset= GBK" %>。该设置和response.setCharacterEncoding("GBK") 等效(jsp文件头指定的contentType最终也反映在response上)。tomcat获取这个相应的类,并且输出的字节流就是对应的编码,同样它把编码名隐藏在HTTP头中传输给browser,以便显示。

  而对于静态网页,因为tomcat直接按照我们保存在机器上的html文件来读取字节流,无法知道对应的字节流的用的什么编码,因此需要显示采用META设置来告诉浏览器,指定网页使用的编码 。例如:<META http-equiv="Content-Type" content="text/html; charset=GBK"/>

    如果同时采用了jsp输出和meta设置两种编码指定方式,则jsp指定的优先。因为jsp指定的直接体现在response中。只要这个设定跟load出来的字节流编码一致就能正确显示网页。

  譬如一个中文静态网页.html文件,保存的时候用的是ANSI,而其meta中的charset用的却是UTF-8,这个时候用浏览器打开,就会出现乱码,因为load出来时ANSI字节流却试图用UTF-8去对应codepage得到unicode字符。如果charset用的是GBK(中文默认的ANSI),这样就会按照得到的字节根据codepage自动查找相应的UNICODE字符并正确显示。

  所谓,codepage,就是一个ANSI编码与UNICODE编码的对应。譬如'中'在gbk中编码为D6 D0,而在UNICODE中为u4E2D,这个时候再codepage中就有一行了
0x4E2D    #CJK UNIFIED IDEOGRAPH 0xD6D1,这样就能正确找到UNICODE字符。


  下面讲解网页传参乱码。
  因为如上所述,在html中可以设定charset,一旦设定这个charset,网页中所有编辑框上输入的字符将以对应的编码转换为字节流输入(当然也可以单独设定,例如: <form accept-charset= "GB2312"> )。因此,如果设定的charset中不包含中文,譬如ISO-8859-1,中文字符将无法正确编码传参输出!
  jsp中,request.getparameter,将返回一个unicode字符,如果没有设定request的characater编码方式,默认按照iso-8859-1,查找iso-8859-1的codepage,即与UNICODE的映射表,将该字节流(其实这个映射很简单,就是每个字节前面补两个0,这样就是两个字节了一个UNICODE字符了)转换为对应的unicode字符串。这是就极有可能产生乱码。如果这个参数有中文,且编码方式是gbk,必然出现乱码!
  有两种方法来解决这个问题,一种是设置request的setCharacterEncoding,这样getparameter就能根据正确的charset将参数字节流的字节按照codepage匹配转换为unicode字符串。另一种方式是将该乱码字符串转化为原始的字节流,然后按照正确的编码方式对应codepage将这些字节进行转换得到unicode字符串。即string.getBytes("iso-8859-1") 得到原始的字节串,再用 string = new String(bytes, "GB2312") 来生成正确的UNICODE字符串。

  好了,到此为至,基本上把字符编码搞得有个一般清楚了。主要参考了这篇经典文章:字符,字节和编码

  对于jsp文件的相关编码设置项研究,请参看下一篇日志测验jsp的几个字符相关设置
标签:无分类:未分组阅读:2995
评论
暂无评论
添加评论
您的大名,限长10汉字,20英文(*)
电子信箱(*)
您的网站
正文,限长500汉字,1000英文(*)
验证码(*) 单击刷新验证码
联系我
博客订阅