考虑编码问题的思路图如下:
针对上图“程序”的类型,下面分别论述:
普通程序:
此类型是最简单的,只要清楚输入编码,程序如何处理,以及将要以何种编码输出即可。不过要注意此处的“输出编码”,因为程序的“输出”即为别的程序的“输入”,有三种特别的“输出目标”,讨论如下:
·控制台
一般控制台的编码是系统默认编码(win一般为GBK),所以输出时要将其转变为该编码,但有些语言(如java)不需直接转变为控制台编码,因为在其内部用的是unicode编码,所以只需将输入的编码进行正确的解码(也就是保证内存中的unicode编码正确即可),其输出函数会自动将其转变为控制台相应的编码。
·web服务器程序
和服务器程序通信一般有两种方式:get(放到URL中)和post(放到消息体中)。所以要清楚以什么方式、什么编码向服务器发送数据即可。
·数据库
数据库其实也是一种服务器程序。普通程序和数据库以何种编码格式通讯,一般程序会通过相应的驱动程序和数据库通讯。常用的驱动类型ODBC和JDBC,会针对不同的数据库有不同的处理方式,能在相应数据库的手册中查询。如JDBC,连接时,如指定连接编码格式,则他们用该编码通讯,如没有指定,JDBC会自动检测所要连接的数据库当前的运行编码(如mysql运行时的服务器级别的编码,下面有详细论述)作为此次连接通讯编码。
浏览器:
因为浏览器是和web服务器通信的,所以其往服务器发送数据相应有两种方式get(放到URL中)和post(放到消息体中)两种。
浏览器会根据其当前解析当前网页的编码对get和post方式发送的数据进行编码。一般浏览器解析网页时会根据该网页的<metahttp-equiv="content-type" content="text/html; charset=UTF-8">,但有时也会不同。其会按照浏览器的解析编码为准,就是说浏览器目前以什么编码解释该网页,可以通过手动设置,如下图为google浏览器的设置编码,但一般都会相同,只要该网站建站时,程序编写正规,一般都会相同,对于jsp页面,浏览器会根据<%@pagepageEncoding="UTF-8"%>或<%@page contentType="text/html;charset=UTF-8"%>进行解析,如果没有指定则默认为ISO-8859-1,可以查看jsp被编译后的servlet文件,其中会以_jspService()函数中的
response.setContentType("text/html;charset=GBK");语句为准,而对于普通的servlet文件也是按照其中的该语句为准,跟后面的
out.write("<metahttp-equiv=\"Content-Type\" content=\"text/html;charset=GBK\">\n");语句没有关系。
对于get方式,有一种特殊方式,就是通过连接方式,如:
<ahref="http://localhost:8080/WebApplication1/NewServlet?name=中文">我是中国人</a>在这里也会按照浏览器的解析编码为准,同上一样,就是说浏览器目前以什么编码解释该网页,然后此处的中文会以什么编码发到服务器,如下图所示可通过手动设置。
Js里有三个函数可以对字符串进行编码,encodeURI ,encodeURIComponent 都是采用UTF-8进行对参数编码,而escape 方法返回一个包含了 charstring 内容的字符串值( Unicode 格式)。可以将字符串进行编码后在输出到网页上,或者讲过编码后利用ajax发到服务器。在此需说明如果利用jquery进行ajax通信,jquery在发送之前都先把数据通过encodeURIComponent转换成UTF-8,所以通过jquery的ajax传到服务器的数据编码(不管get还是post)均为UTF-8。
web服务器程序(在此只讨论tomcat):
有两种方式接收参数,get和post。针对这两种方式,web服务器会进行不同的处理(即解码):
·get方式接收参数
Tomcat会根据server.xml配置文件中设置的编码进行解码,如果没有设定,其默认会以ISO-8859-1进行解码。<ConnectorURIEncoding="UTF-8" port="8080"protocol="HTTP/1.1" connectionTimeout="20000"redirectPort="8443" />
·post方式接收参数
Tomcat会根据servlet中获取参数之前的request.setCharacterEncoding("GBK");来进行解码,如果没有设定,其默认会以ISO-8859-1进行解码。
数据库(在此只论述mysql,其他数据库可根据其用户手册查询):
Mysql服务器的字符集和校对规则有4个级别的默认设置:服务器级、数据库级、表级和连接级。
服务器级:在运行时参数character_set_server体现,可通过set命令改变;
当服务器启动时根据有效的选项设置:mysqld --default-character-set=latin1;
可以在配置文件my.ini设置:[mysqld]下的default-character-set;
数据库级:创建数据库时没有指定编码时会从服务器级继承,在运行时参数character_set_database体现,可通过set命令改变;
表级:创建表时没有指定编码会从数据库级继承;
连接级:该级别决定客户端和服务器端的通信编码,对于应用程序开发很重要;
命令行进行登录:
其默认编码由My.ini配置文件中[mysql]下的default-character-set决定,此选项会影响运行时参数character_set_client、character_set_connection、character_set_results。服务器接收到参数时会从character_set_client编码解析到character_set_connection编码。可以通过set命令改变这三个参数,而参数character_set_results决定服务器以什么编码将结果发送到客户端。
SET NAMES 'charset_name'相当于:
mysql> SET character_set_client = charset_name;
mysql> SET character_set_results = charset_name;
mysql> SET character_set_connection = charset_name;
SET CHARACTER SET charset_name相当于:
mysql> SET character_set_client = x;
mysql> SET character_set_results = x;
mysql> SET collation_connection = @@collation_database;
如果不希望服务器往客户端返回结果数据执行任何转换,设置character_set_results为NULL:
mysql> SET character_set_results = NULL;
JDBC连接:
其实当一个客户端连接时,它向服务器发送希望使用的字符集名称。服务器为那个字符集设置character_set_client、character_set_results和character_set_connection变量。(实际上,服务器为使用该字符集执行一个set names操作。)
对于从JDBC驱动程序发往服务器的所有字符串,均将自动地从固有Java内存中的 Unicode形式转换为客户端字符编码(character_set_client),包括通过Statement.execute()、Statement.executeUpdate()和Statement.executeQuery()发出的所有查询,以及除了用setBytes()、setBinaryStream()、setAsiiStream()、setUnicodeStream()和setBlob()排除的参试之外的所有PreparedStatement和CallableStatement参数。
在MySQL服务器4.1之前,Connector/J支持每连接单一字符编码,能够从服务器配置自动检测到它(aotudetect),也能由用户通过使用useUnicode和characterEncoding属性配置它。
从MySQL服务器4.1版起,Connector/J支持客户端和服务器之间的单一字符编码,以及针对结果集中从服务器返回至客户端的数据的任意数目字符编码。
连接时将自动检测客户端和服务器之间的字符编码。对于由驱动程序使用的编码来说,它是在服务器上通过使用配置变量“character_set”(低于4.1.0的服务器版本)和“character_set_server”(4.1.0和更高的服务器版本)指定的。
要想覆盖客户端上的自动检测编码功能,可在用于连接到服务器的URL中使“characterEncoding”属性。
在客户端上指定字符编码时,应使用Java风格名称。在下面的表格中,列出了用于MySQL字符集的Java风格名称:
MySQL字符集名称 | Java风格字符编码名称 |
usa7 | US-ASCII |
big5 | Big5 |
gbk | GBK |
sjis | SJIS |
gb2312 | EUC_CN |
ujis | EUC_JP |
euc_kr | EUC_KR |
latin1 | ISO8859_1 |
latin1_de | ISO8859_1 |
german1 | ISO8859_1 |
danish | ISO8859_1 |
latin2 | ISO8859_2 |
czech | ISO8859_2 |
hungarian | ISO8859_2 |
croat | ISO8859_2 |
greek | ISO8859_7 |
hebrew | ISO8859_8 |
latin5 | ISO8859_9 |
latvian | ISO8859_13 |
latvian1 | ISO8859_13 |
estonia | ISO8859_13 |
dos | Cp437 |
pclatin2 | Cp852 |
cp866 | Cp866 |
koi8_ru | KOI8_R |
tis620 | TIS620 |
win1250 | Cp1250 |
win1250ch | Cp1250 |
win1251 | Cp1251 |
cp1251 | Cp1251 |
win1251ukr | Cp1251 |
cp1257 | Cp1257 |
macroman | MacRoman |
macce | MacCentralEurope |
utf8 | UTF-8 |
ucs2 | UnicodeBig |
警告
不要用Connector/J发出查询“set names”,这是因为驱动程序不会检测已变化的字符集,而是会继续使用在初始连接设置中检测到的字符集。
为了允许从客户端发出的多个字符集,应使用“UTF-8”编码,方式是,将utf8配置为默认的服务器字符集,或通过“characterEncoding”属性配置JDBC驱动程序以使用“UTF-8”。
最后说一下Mysql运行时关于编码的五个参数
可以通过show variables like'%char%';显示:
character_set_client
character_set_connection
character_set_results
character_set_database
character_set_server
character_set_system
MySQL中的字符集转换过程
1. MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;
2. 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集,其确定方法如下:
使用每个数据字段的CHARACTER SET设定值;
若上述值不存在,则使用对应数据表的DEFAULTCHARACTER SET设定值(MySQL扩展,非SQL标准);
若上述值不存在,则使用对应数据库的DEFAULTCHARACTER SET设定值;
若上述值不存在,则使用character_set_server设定值。
3. 将操作结果从内部操作字符集转换为character_set_results。
常见问题解析
向默认字符集为utf8的数据表插入utf8编码的数据前没有设置连接字符集,查询时设置连接字符集为utf8
– 插入时根据MySQL服务器的默认设置,character_set_client、character_set_connection和character_set_results均为latin1;
– 插入操作的数据将经过latin1=>latin1=>utf8的字符集转换过程,这一过程中每个插入的汉字都会从原始的3个字节变成6个字节保存;
– 查询时的结果将经过utf8=>utf8的字符集转换过程,将保存的6个字节原封不动返回,产生乱码……
向默认字符集为latin1的数据表插入utf8编码的数据前设置了连接字符集为utf8
– 插入时根据连接字符集设置,character_set_client、character_set_connection和character_set_results均为utf8;
– 插入数据将经过utf8=>utf8=>latin1的字符集转换,若原始数据中含有\u0000~\u00ff范围以外的Unicode字 符,会因为无法在latin1字符集中表示而被转换为“?”(0x3F)符号,以后查询时不管连接字符集设置如何都无法恢复其内容了。
使用MySQL字符集时的建议
建立数据库/表和进行数据库操作时尽量显式指出使用的字符集,而不是依赖于MySQL的默认设置,否则MySQL升级时可能带来很大困扰;
数据库和连接字符集都使用latin1时虽然大部分情况下都可以解决乱码问题,但缺点是无法以字符为单位来进行SQL操作,一般情况下将数据库和连接字符集都置为utf8是较好的选择;
使用mysql C API时,初始化数据库句柄后马上用mysql_options设定MYSQL_SET_CHARSET_NAME属性为utf8,这样就不用显式地用 SET NAMES语句指定连接字符集,且用mysql_ping重连断开的长连接时也会把连接字符集重置为utf8;
对于mysql PHP API,一般页面级的PHP程序总运行时间较短,在连接到数据库以后显式用SET NAMES语句设置一次连接字符集即可;但当使用长连接时,请注意保持连接通畅并在断开重连后用SET NAMES语句显式重置连接字符集。
其他注意事项
my.cnf中的default_character_set设置只影响mysql命令连接服务器时的连接字符集,不会对使用libmysqlclient库的应用程序产生任何作用!
对字段进行的SQL函数操作通常都是以内部操作字符集进行的,不受连接字符集设置的影响。
SQL 语句中的裸字符串会受到连接字符集或 introducer 设置的影响,对于比较之类的操作可能产生完全不同的结果,需要小心!
其中配置文件my.ini中,[mysql]下的default-character-set影响前三个,[mysqld]下的default-character-set影响第4和第5个。
下面说一下最后一个参数,它是关于数据库元数据的:
元数据是“关于数据的数据”。描述数据库的任何数据—作为数据库内容的对立面—是元数据。因此,列名、数据库名、用户名、版本名以及从SHOW语句得到的结果中的大部分字符串是元数据。还包括INFORMATION_SCHEMA数据库中的表中的内容,因为定义的那些表存储关于数据库对象的信息。
元数据表述必须满足这些需求:
·全部元数据必须在同一字符集内。否则,对INFORM一个TION_SCHEMA数据库中的表执行的SHOW命令和SELECT查询不能正常工作,因为这些运算结果中的同一列的不同行将会使用不同的字符集。
·元数据必须包括所有语言的所有字符。否则,用户将不能够使用它们自己的语言来命名列和表。
为了满足这两个需求,MySQL使用Unicode字符集存储元数据,即UTF8。如果你从不使用重音字符,这不会导致任何破坏。但如果你使用重音字符,应该注意的是元数据是用UTF8存储。
这意味着,USER()、CURRENT_USER()、DATABASE()和VERSION()函数的返回值被 默认设置为UTF8字符集,这与同义函数如SESSION_USER()和SYSTEM_USER()的结果相同。
服务器将character_set_system系统变量设置为元数据字符集的名:
mysql> SHOWVARIABLES LIKE 'character_set_system';
+----------------------+-------+
|Variable_name | Value |
+----------------------+-------+
|character_set_system | utf8 |
+----------------------+-------+
存储元数据使用Unicode并不意味着列头和DESCRIBE函数的结果默认在character_set_system字符集中。当你使用SELECT column1 FROM t语句时,名字为column1的列从服务器返回客户端并使用由SET NAMES语句确定的字符集。更明确地说,使用的字符集是由character_set_results系统变量的值确定的。如果这个系统变量设置为NULL,不执行字符转换,服务器使用最初的字符集(字符集由character_set_system系统变量设置)返回元数据。
如果你希望服务器不使用UTF8字符集返回元数据结果,那么使用SET NAMES语句强制服务器执行字符集转换,或者在客户端执行转换。在客户端执行转换效率较高,但这种选项并不能使用于全部客户端。
如果你正在一个语句中使用(例如)USER()函数进行比较或赋值,不要担心。MySQL为你执行一些原子转换。
SELECT * FROMTable1 WHERE USER() = latin1_column;
这是可以的,因为在比较之前latin1_column列的内容会自动转换到UTF8。
INSERT INTO Table1(latin1_column) SELECT USER();
这是可以的,因为赋值之前USER()函数返回的内容自动转换为latin1。至今,自动转换没有全部实施,但是以后的版本中应该工作正常。
尽管自动转换不属于SQL标准,SQL标准化文档中说每一个字符集是(根据支持的字符)Unicode的“子集”。因此,一个知名的原则是,“适用超集的字符集能够应用于其子集”,我们相信Unicode的校对规则能够应用于非Unicode字符串的比较。
注释:在MySQL5.1中,errmsg.txt文件全部使用UTF8。客户端字符集的转换是自动进行的,如同元数据。