关闭

无处不在的字符集

2862人阅读 评论(0) 收藏 举报

无处不在的字符集

lu_yi_ming(_at_)sina.com  2004.12
0.2 版 (本文中可能有很多错误,仅供参考,多谢指点纠正)


  一、字符集应用举例:网页浏览

  我们从用户用 IE 浏览一个 Html 页面开始,假设这是一个“用户信息登记”网页,用户输入姓名、年龄等信息进行注册。

  用户启动 IE 后在地址栏输入网址后(与键盘输入有关的字符集处理请参考后面"用户输入姓名"),IE 把网址以 UTF16 存入缓存,然后再将网址转换成 UTF8,继续打包成 Http 数据包(Http 协议的字符集处理略),交给 TCP/IP Socket 去发送给 Web 服务器(之前的服务器 DNS 解析的字符集问题略,中文域名的字符集问题略)。

  Web 服务器(服务器处理文件系统的字符集问题见后“加载 jsp 文件”)返回包含 Html 文件(为了支持国际化这个文件用 UTF8 编码保存)的 Http 数据包,通过 TCP/IP socket 返回给 IE,IE 在 Http 协议中找内容字符集设定,如果找不到则再从 Html 文件中找定义字符集的字符串 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">,统统找不到就默认为 ISO-8859-1,总而言之,IE 为当前的 Html 文件认定了一个字符集(我们假定为 UTF8),之后,按字符集将 Html 文件显示在 IE 的窗口中(文字显示的字符集问题请参考下面"用户输入姓名")。

  IE 按正确的字符集显示了 Html 文件,用户开始在一个 <input type="text"> 中输入姓名,每一次按键盘后 Windows 将虚拟键码加到 WM_KEYDOWN 消息中发到 IE 的消息队列中,GetMessage() 取出消息后交给 TranslateMessage() 处理,TranslateMessage() 将虚拟键码交给输入法窗口去处理,用户在输入法窗口选择的汉字以 UTF16 的编码暂存下来,然后根据 IE 窗口注册类的字符集类型(UTF16/GBK)以对应的编码把汉字编码加到 WM_CHAR 中发到 IE 的消息队列中(发给 IE 的是 UTF16,一个汉字一个消息;如果是 GBK,就是两个消息),IE 把 UTF16 的汉字编码存入内存缓冲,之后用编码、字体、显示位置等作为参数调用 TextOut(),TextOut() 在字体库中找到 UTF16 编码对应(如果字体库内部不是 UTF16 编码则先要进行字符集转换)的矢量/点阵图形,显示在 IE 窗口中的特定位置( <input type="text">中)。

  用户按“提交”按钮。

  用户在网页上输入的所有文字信息都已经按 UTF16 的编码存在 IE 的缓存中。用户按“提交”按钮后,IE 把缓存中的数据(Form 中的各项)转换成先前认定的 Html 的字符集(先前假定 UTF8),然后打包在 Http 包中(Form 的 action 的网址依然转换成 UTF8),交给 TCP/IP  socket 去发送(有关字符集的处理同前)。

  我们假设提交给了一个 Linux 上运行的 J2ee Server(Tomcat) 的一个 jsp 文件进行处理。

  Tomcat/JVM 的 TCP/IP socket 接收到 Http 包,因为 JVM 只能处理 UTF16 的字符,所以 Tomcat 将 Http 包整个转换为 UTF16 开始分析,分析后知道需要加载一个 jsp 来处理,而此 jsp 尚未加载,于是 Tomcat/JVM 将文件名(包括路径)转换为 UTF8(因为 Glibc/Linux 内核需要 UTF8)后,调用 Glibc/Linux 内核的 open() 来打开文件(Linux 内核访问文件系统的字符集转换略,或本文其他部分另有讨论)。Tomcat/JVM 继续调用 Glibc/Linux 内核的 read() 把文件的二进制数据读入内存,把文件的开始部分按 ISO-8859-1 字符集转换成 UTF16 ,然后在其中寻找字符串 <%@ page contentType="text/html; charset=utf-8" %> 来确定此文件的字符集(此文件为了支持国际化也用 UTF8 编码保存)。Tomcat/JVM 把整个 jsp 文件的数据都按 UTF8 转换为 UTF16,然后翻译成 java 格式,再将 java 格式的文件数据转换回 UTF8,另存成 java 文件放在工作目录下(文件系统字符集转换同上)。Tomcat 调用 javac 把 java 文件编译成 class 文件(编译过程中也进行了字符集转换,否则 C 库或操作系统不支持,但具体情况不明。代码中的字符串被转换成 UTF16),然后读入内存加载(以下简称 jsp 代码)。

  Tomcat 把来自用户的 Http 包中的主要数据二进制形式提交给 jsp 代码处理,jsp 代码首先将二进制数据按 UTF8 转换成 JVM 的内码 UTF16,然后读出 Form 中的各字段文字,进行适当的数据类型转换,开始处理。

  我们假设 jsp 代码要将用户的数据存入一个数据库(MySQL)。

  jsp 代码加载 MySQL 的 JDBC 的驱动程序,连接到一个 MySQL 的服务器的数据库(名字为 DB)上(连接服务器时 TCP/IP 相关的字符集转换略)。JDBC 驱动程序与 DB 建立连接后,立即从服务器得到了 DB 的默认字符集(为了支持国际化设定为 UTF8)。jsp 代码把用户 Form 中得来的数据组成一个 SQL 语句(当然还是 UTF 16) INSERT table1 (name, company) values ('名字', '单位') ,交给 JDBC 去执行。JDBC 先向服务器询问 name 和 company 两个字段的字符集(name的字符集为 UTF8,company 的字符集为 GBK),然后把 INSERT ... 语句的转换成 UTF8,但是其中的“单位”被转换成 GBK,之后把这个字符串数据通过 TCP/IP 发到服务器端,服务器端把数据存在文件中(字符集不变化),UTF8 的“名字”占 6 字节,GBK 的“单位”占 4 字节。

  假设 jsp 代码下一步应该从数据库中取得一些信息,然后向用户的 email 信箱中发一封确认邮件,以便用户确认。

  jsp 代码准备好一个 SQL 语句 SELECT ...,交给 JDBC 运行要求返回一个结果集,JDBC 把 SELECT 语句转换字符集后后发到 MySQL DB 数据库(具体字符集转换与前述类似),服务器返回一个结果集,JDBC 接收下来,jsp 代码从结果集中 GetString(),JDBC 进行必要的字符集转换后返回一个 UTF16 的字符串。jsp 根据得到的数据计算出来用于发邮件的字符串,调用 javamail 的 MimeMessage.setText() 等等设置好邮件,到这里字符一直是 UTF16 编码。邮件准备好后 jsp 调用 Transport.send() 发出邮件。Transport.send 在把邮件的数据包交给 TCP/IP socket 之前根据 Linux 的环境变量中的字符集定义进行转换。具体的邮件的发送的字符集转换处理及 SMTP 协议的字符集处理略。

  邮件发送成功后,jsp 代码给用户显示一些提示信息,作为新用户登记的结束。

  jsp 代码准备给用户返回一个 Html 页面,首先指定 Http 协议中所需定义的字符集,这里继续返回 UTF8 的页面 <%@ page contentType="text/html; charset=utf-8" %>,然后 out.println() 输出要显示的 Html 信息,Tomcat 把所有的输出缓冲区中的信息从 UTF16 转换为 UTF8,然后通过 TCP/IP socket 返回给 IE,IE 按前述的字符集转换后,显示给用户。


  二、字符集分类简述

  1. 基本定义

  ◇ GBK 是 GB2312 的超集。GBK 中一个英文字母占 1 个字节,一个中文字符占 2 个字节,中文按拼音排序,字库中包括繁体字。

  ◇ Unicode 通常指 Unicede 16。还有 Unicode 32,不常用。一个字符无论中英均占两个字节,高字节在后。中文按偏旁部首笔划排序,字库中包括繁体字。

  ◇ UTF-8 是 Unicode 16 的变种,与 Unicode 16 一一对应,所以排序规则相同。一个英文字母占 1 个字节,一个中欧字符占 2 个字节,一个中文字符占 3 个字节。字符串比较、排序等运算较 Unicode 16 可能慢 30%(仅是参考)。

  2. Windows(2000)系统

  ◇  我们在 Windows 上看到的几乎每一个字都是 Textout 函数(ExTextout)输出的,Textout 内部(Windows 内部)使用 UTF-16/UCS-2 little endian (来自MSDN)。Textout 显示在屏幕上的字符图形来自字体文件,一个字体文件为每一个字符准备了一幅图形(矢量的或点阵的),当然,字体文件内部写明了其字符属于哪个字符集。所以,如果字体文件的字符集不是 UTF-16/UCS-2 little endian,那么 Textout 还要先进行转换,以便找到准确的字符图形显示到屏幕上。

  ◇ Windows 内部准备了很多字符集(各字符集与 UTF-16 的对应库),用于支持应用程序。

  ◇ Windows 还有一个默认的本地字符集(最终用户字符集),用来支持本地化。中文 Windows 的本地字符集是 GBK

  ◇ Windows 涉及到字符的 API 都有两套( ...W 和 ...A),...W 用于 Unicode,...A 用于本地 GBK

  ◇ 在一个 Windows 的应用程序中,你利用键盘输入了一个中文字符(利用输入法工具),TranslateMessage 函数会把它翻译成 GBK 或 Unicode 字符(根据 RegisterClass 决定),然后附加到 WM_CHAR 后(wParam)发给你的应用程序。

  ◇ Windows 自带的记事本 Notepad.exe 。从键盘上输入的中文被翻译成 Unicode 字符,存入内存缓冲。保存文件的时候,可以选择编码,如果选择 ANSI,就翻译保存成 GB2312;如果选择 Unicode ,就直接保存成 Unicode;如果选择 UTF-8,就翻译保存成 UTF-8。这几种字符集的文本文件都可以打开。

  ◇ 保存在 FAT/32 分区中的文件名的字符集是 ANSI/OEM(也就是GBK/GB2312),保存在 NTFS 分区中的文件名的字符集是 Unicode

  ◇ Windows 的 DOS 窗口可以显示 GBK/GB2312、UTF-16 的文本文件。

  ◇ 显示一个文件的二进制数据可以用 VC 的编辑器。

  3. Linux 系统(Redhat FC2 / Kernel 2.6.6)

  ◇ Linux 内核字符集是 UTF-8。

  ◇ Linux 用户界面(shell)的字符集可以在环境变量中设定(/etc/profile),默认是en_US.UTF-8,可以更改为如: export LC_TYPE="zh_CN.GB2312"。

  ◇ 显示其他字符集的文件的内容可以用 iconv 命令,如 cat ... | iconv -f gb2312 -t utf-8

  ◇ KDE 上的文本编辑器 KWrite 很好,可以在打开文件、保存文件时选择字符集进行翻译。

  ◇ 显示一个文件的二进制数据的命令是 hexdump -C

  ◇ fstab 中 FAT/32 的分区的写法
    /dev/hdb1 /mnt/winc vfat defaults,codepage=936,iocharset=cp936 0 0
    /dev/hda5 /mnt/wind vfat defaults,codepage=936,iocharset=cp936 0 0

  ◇ 把另一个 Windows 机器上的一个共享目录映射成本地 Linux 的一个子目录
    mount -t smb -o charset=gb2312,codepage=cp936,username=xxx //pcname/share /mnt/dir1

  4. 网页浏览

  ◇ Http 协议部分字符集是 utf-8,数据部分的字符集在协议中指定,或在 Html 文件中指定。

  ◇ Html 文件的字符集在其文件内部指定,<meta http-equiv="Content-Type" content="text/html; charset=unicode">。多乱啊,浏览器还不知道接收到的数据块的字符集,却要到其内部找出这么复杂的一个串,然后才知道自己找到的是什么字符集。
  做个测试:用 FrontPage 随便编辑几行中文,设置网页字符集为 unicode(网页属性->文字->编码),然后保存成一个文件,然后二进制方式打开这个文件,把开头表示 Unicode16 的两个字节 "FF FE"删掉,再保存。在打开或浏览,完了,IE / FrontPage / InterDev 都不认识了,只有 FireFox 正常显示,FireFox 真好。看来 IE 等都是通过文件头来判断字符集的,有时候那句 HTML 字符集的标记根本不起作用。

  ◇ 在 Windows 上 IE、FireFox 都把用户键入的字符翻译成 Unicode 保存,包括地址栏及表单。地址栏的信息发送到服务器前按 Http 协议的字符集转换。表单的信息发送到服务器(应用程序的服务器端)前按 Html 中指定的字符集(或浏览器认定的字符集)转换。

  5. Java/J2EE

  ◇ JVM 内部的字符集是 Unicode

  ◇ Java 源程序文件的字符集可任意,通常由编辑器保存文件时确定(系统用户界面的默认字符集)。javac 编译时可以指定 java 源文件的字符集,如果不指定就取环境变量或系统的默认字符集。.class 文件的字符集是 Unicode。

  ◇ 一个 Java 字符串 (String) 变换字符集的代码(从 gb2312 转换到 utf-8)是: String str2 = new String(str1.GetByte("gb2312"), "utf-8");

  ◇ Jsp 文件的字符集可任意,加载编译时会从文件首的 <@ ... > 寻找字符集定义。

  ◇ Linux 上,javamail 发出的邮件的字符集默认会转换成本地系统的字符集,环境变量中定义的。

  6. VC/.Net

  ◇ 略

  7. 数据库

  7.1 MySQL (4.1.8 )

  ◇ MySQL 服务器端启动时可指定字符集,启动时的字符集设定可写在配置文件中,简单的做法是在图形化管理工具 MySQL Administrator 中设定。

  ◇ MySQL 的每个库(DB)、表、字段都可以设定不同的字符集。如果支持国际化(如 Name 字段要保存中文、英文、日文、韩文、阿拉伯文等等多种语言的人的名字),则整个采用 utf-8 的字符集比较好。

  ◇ MySQL 针对每种字符集有若干种排序方法,但对 utf-8 没有汉字的拼音排序(utf-8 汉字按偏旁部首笔画排序)。对于 utf-8 字段的中文按拼音排序的变通做法是增加一个 gbk 字符集的字段,每次把相同的数据分字符集同时插入两个字段,对于中文部分,则可以按后一个字段排序。

  ◇ MySQL 的 JDBC 驱动程序很“聪明”,会自动在 Java 中的 Unicode 字符集与数据库的字符集之间进行转换。

  ◇ MySQL 的图形化客户端 MySQL Query Browser 在处理字符集自动转换方面也很聪明。

  ◇ MySQL 的字符客户端 mysql 在处理字符集自动转换方面很差。

  8. 电子邮件

  ◇ 本版略,下一版详述。

  9. SSH

  ◇ 略

  任何一个字符都属于一个字符集,字符集无处不在。

0
0

猜你在找
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:72577次
    • 积分:1181
    • 等级:
    • 排名:千里之外
    • 原创:46篇
    • 转载:1篇
    • 译文:0篇
    • 评论:10条
    文章分类
    最新评论