2012年(6)
分类: 系统运维
2012-05-30 17:19:46
目录
TOC \o "1-3" \h \z \u
编码基本知识
在不同的平台上跨越的时候,编码,字符集是一个很头疼的问题.因为编码的不同,乱码会经常出现.乱码的根本问题在于对于同样一段内容,采用了不同的编码去解读.大部分的跨越应该都发生在windows平台和类linux平台上. Linux下得文件格式一般默认是UTF-8,而Windows下的文件格式默认是ANSI。简体中文的Windows系统里,ANSI对应的是GB2312;繁体中文的Windows系统ANSI对应的是Big5。繁体中文的big5和简体中文的gb系列是不兼容的,大家用term上bbs的时候会遇到过这样的问题.如果使用big5,就满屏的乱码.
编码介绍对于各种编码的原理,这里摘抄了他人的一段,简单的介绍:
最早的编码是iso8859-1,和ascii编码相似。但为了方便表示各种各样的语言,逐渐出现了很多标准编码,重要的有如下几个。
iso8859-1属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。比如,字母'a'的编码为0x61=97。很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。比如,虽然"中文"两个字不存在iso8859-1编码,以gb2312编码为例,应该是"d6d0 cec4"两个字符,使用iso8859-1编码的时候则将它拆开为4个字节来表示:"d6 d0 ce c4"(事实上,在进行存储的时候,也是以字节为单位处理的)。而如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。很明显,这种表示方法还需要以另一种编码为基础。
GB2312/GBK这就是汉子的国标码,专门用来表示汉字,是双字节编码,而英文字母和iso8859-1一致(兼容iso8859-1编码)。其中gbk编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,gbk是兼容gb2312编码的。
unicode这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。所以可以说它是不兼容iso8859-1编码的,也不兼容任何编码。不过,相对于iso8859-1编码来说,uniocode编码只是在前面增加了一个0字节,比如字母'a'为"00 61"。需要说明的是,定长编码便于计算机处理(注意GB2312/GBK不是定长编码),而unicode又可以用来表示所有字符,所以在很多软件内部是使用unicode编码来处理的,比如java。
UTF考虑到unicode编码不兼容iso8859-1编码,而且容易占用更多的空间:因为对于英文字母,unicode也需要两个字节来表示。所以unicode不便于传输和存储。因此而产生了utf编码,utf编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符,不过,utf编码是不定长编码,每一个字符的长度从1-6个字节不等。另外,utf编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。
注意,虽然说utf是为了使用更少的空间而使用的,但那只是相对于unicode编码来说,如果已经知道是汉字,则使用GB2312/GBK无疑是最节省的。不过另一方面,值得说明的是,虽然utf编码对汉字使用3个字节,但即使对于汉字网页,utf编码也会比unicode编码节省,因为网页中包含了很多的英文字符。
引用地址: http://hi.baidu.com/wenqinhi/blog/item/a2b5f502c52382ff09fa9342.html
说明其实我们主要需要讨论的就是gbk和utf-8的转换.了解了这两种,别的举一反三即可.简单的说明一下:
l iso8859,就是传说中的latin,主要是用在欧洲那一片的字符集.跟中文没啥关系.有很多档,可以在维基百科上查到.前128个字符是兼容ASCII的.后面我们会提到file命令会把gbk认成这个.
l windows上的简体中文一般是用gb2312.一般来说,我们在xp上的操作系统,文件内容,程序,程序输入等等都是gb2312.gb2312字符集比较小,所以后来有了gbk.为了方便,在后面我们都只讨论gbk.大家在用输入法的时候可以看到,很多字要调到gbk才能打出来,比如"嬲","嫐"这种.而gbk是兼容gb2212的,而且gbk可以用繁体字.但gbk和big5还是不可能兼容的.
l 注意,Unicode是一种字符集,而utf-8是一种编码方式.所以utf-8才是我们需要不断讨论的.Unicode的思想是大统一.大统一有很多种办法,每一种办法就是一种编码方式.现在最推崇的就是utf-8这样的编码.如果开发的软件或者脚本就我们自己用用,gbk和utf8之类的随便选就可以了.但如果要国际化,远送沙特东渡日本,就一定要用utf-8(或者其它utf系列)了.
终端,程序,与文件 终端与文件
本文的环境境是xp下的securetCRT登redhatlinux.绝大部分linux安装中文支持的话,中文编码一般都是使用utf-8.终端是影响编码的一个很重要的因素.secureCRT的编码设置如下:
默认是default.经测试,default就是gbk.所以如果没有设置终端编码,你将会以gbk编码来操作linux.这会是一个什么样的现象,我们来看看.我们生成一个文件ff,写一段中文,然后保存后,再用cat打开(为了查看方便,我设置了PS1环境变量显示当前终端的编码.其实是一个用户):
[gbk@10.81.52.135]~/code>$ cat > ff
中国已经完蛋了
[gbk@10.81.52.135]~/code>$ cat ff
中国已经完蛋了
我们另起一个终端登入机器,这个终端我们设为utf-8.打开这个文件,看到:
[utf8@10.81.52.135]~/code>$cat ff
?1?K
这就是乱码.使用file命令查看文件编码:
[gbk@10.81.52.135]~/code>$ file ff
ff: ISO-8859 text
可以看出,file会打出iso-8859.file并不会完全正确的去判断文件的编码类型,它把gbk当作ISO-8859,可能是因为gbk在某此范围内是与该字符集兼容的.一般来说,包含中文的文件被file判断成ISO-8859,就说明该文件是gbk编码.因为iso-8859所有系列都没有一个是支持中文的编码流.
如果我们使用iconv将文件转码:
[gbk@10.81.52.135]~/code>$iconv -fgbk -tutf8 ff > aa
[gbk@10.81.52.135]~/code>$file aa
aa: UTF-8 Unicode text
[gbk@10.81.52.135]~/code>$cat aa
涓?浗宸茬粡瀹岃泲浜?
在utf8环境下查看aa
[utf8@10.81.52.135]~/code>$cat aa
中国已经完蛋了
看到,iconv将gbk文件转成utf8,生成的aa文件在utf8终端下是正常,在gbk下就成了乱码.一起查看两个文件:
[utf8@10.81.52.135]~/code>$ll aa ff
-rw-rw-r-- 1 work work 22 May 29 18:25 aa
-rw-rw-r-- 1 work work 15 May 29 18:13 ff
[utf8@10.81.52.135]~/code>$file aa ff
aa: UTF-8 Unicode text
ff: ISO-8859 text
可以看到,aa文件比较大一点,一共大7个字节.这是因为utf8的每个汉字是用三个字节存的,gbk的每个汉字是两个字节存的.所以一共7个汉字,就差了7字节.utf8虽然存中文很耗空间,但别个存英文,拉丁都只要一个字节,而gbk还是两个.所以utf8在有大量英文少量中文的文件里远比gbk要省空间.比如各种程序代码.
程序说完了终端,我们来说程序.程序如果输出中文,会是个什么样子.我们来做个例子看看.我们在utf8终端下写一个文件a.c,内容如下:
[utf8@10.81.52.135]~/code>$cat a.c
#include
#include
int main(int argc, char* argv[])
{
const char* ss="中国已经冒得救了!!";
FILE* fp = fopen("haha", "w");
if(fp==NULL)
{
printf("error open\n");
exit(-1);
}
fprintf(fp, "%s\n", ss);
fprintf(fp, "%s\n", ss);
fprintf(fp, "%s\n", ss);
printf("%s\n", ss);
fclose(fp);
return 0;
}
[utf8@10.81.52.135]~/code>$file a.c
a.c: UTF-8 Unicode C program text
毫无疑问,它是utf8的.毫无疑问,它在gbk下必是乱码.这个程序很简单,它把一段中文打到文件和终端上.我们在utf8和gbk终端下看看效果:
[utf8@10.81.52.135]~/code>$gcc a.c
[utf8@10.81.52.135]~/code>$./a.out
中国已经冒得救了!!
[utf8@10.81.52.135]~/code>$cat haha
中国已经冒得救了!!
中国已经冒得救了!!
中国已经冒得救了!!
gbk终端下:
[gbk@10.81.52.135]~/code>$./a.out
涓?浗宸茬粡鍐掑緱鏁戜簡!!
[gbk@10.81.52.135]~/code>$cat haha
涓?浗宸茬粡鍐掑緱鏁戜簡!!
涓?浗宸茬粡鍐掑緱鏁戜簡!!
涓?浗宸茬粡鍐掑緱鏁戜簡!!
看看,用utf8的代码编译的文件,在utf8下的终端输出正常.但在gbk下的终端就郁闷了.同样,如果我们使用iconv把a.c改成gbk格式的,可以得到相反的结论.可以看到,如果将中文直接写到源文件里,编译的时候就会以当时文件的编码规范来为该字符串常量编码,程序打出来的是以当是文件编码格式决定的.所以,如果我们习惯在windows上写代码出现这咱情况,保存后同步到linux上,编译,跑起来,而且终端要是utf8的编码,就会乱码.
如果程序从文件里读取再写入会发生什么情况呢,我们同样写了个例子c.c:
[utf8@10.81.52.135]~/code>$cat c.c
#include
#include
#include
int main(int argc, char* argv[])
{
char* ss=NULL;
size_t len=0;
FILE* rfp = fopen(argc>1?argv[1]:"haha", "r");
if(rfp==NULL)
{
printf("error open read file\n");
exit(-1);
}
FILE* wfp = fopen("whaha", "w");
if(wfp==NULL)
{
printf("error open write file\n");
exit(-1);
}
while(getline(&ss, &len, rfp) != -1)
{
// printf("ss: %s\n", ss);
fprintf(wfp, "%s", ss);
//memset(ss, 0, 1024);
}
fclose(rfp);
fclose(wfp);
return 0;
}
编译后,执行的结果如下:
[utf8@10.81.52.135]~/code>$file haha
haha: ISO-8859 text
[utf8@10.81.52.135]~/code>$gcc c.c
[utf8@10.81.52.135]~/code>$./a.out
[utf8@10.81.52.135]~/code>$file haha whaha
haha: ISO-8859 text
whaha: ISO-8859 text
[utf8@10.81.52.135]~/code>$diff haha whaha
[utf8@10.81.52.135]~/code>$cat whaha
?1°μt??!
?1°μt??!
?1°μt??!
可见,写出文件的编码是由读入的编码决定的,程序只是一个字节一个字节的拷贝而已,没有做任何编码上的动作.但是,有个有意思的现象就是,如果在c.c文件里使用utf8再追加一段,即:
[utf8@10.81.52.135]~/code>$cat c.c
#include
#include
#include
int main(int argc, char* argv[])
{
char* ss=NULL;
size_t len=0;
FILE* rfp = fopen(argc>1?argv[1]:"haha", "r");
if(rfp==NULL)
{
printf("error open read file\n");
exit(-1);
}
FILE* wfp = fopen("whaha", "w");
if(wfp==NULL)
{
printf("error open write file\n");
exit(-1);
}
while(getline(&ss, &len, rfp) != -1)
{
// printf("ss: %s\n", ss);
fprintf(wfp, "%s", ss);
//memset(ss, 0, 1024);
}
fprintf(wfp, "哥是utf8上面添加的!!", ss);
fclose(rfp);
fclose(wfp);
return 0;
}
就是红色的那一段,运行一下程序,可以看到,file是多么的绝望:
[utf8@10.81.52.135]~/code>$file whaha
whaha: Non-ISO extended-ASCII text
此时此刻,file已经混乱了.gbk掺杂utf8,就是这个效果.而且,令人震惊的是,如果你打开文件,你会发现这个文件里每个终端都还是能自己认识自己该认识的那一段.我不知道这是不是巧合,我只知道这样的文件千万不能用.
当然,万能的程序并非不能自己做转换,借用iconv函数,可以在代码里做转换.问题在于,你必须知道你读的文件是什么编码:
[utf8@10.81.52.135]~/code>$cat e.c
#include
#include
#include
#include
#include
size_t convert(iconv_t cd, const char* inStr, char* outStr)
{
char* in = (char*)inStr;
char* out = outStr;
size_t inlen = (size_t)strlen(inStr), outlen=1024;
size_t len = iconv(cd, &in, &inlen, &out,&outlen);
return len;
};
int main(int argc, char* argv[])
{
char* ss=NULL;
iconv_t cd = iconv_open("UTF8", "GBK");
FILE* rfp = fopen(argc>1?argv[1]:"haha", "r");
//FILE* rfp = fopen("haha", "r");
size_t len;
if(rfp==NULL)
{
printf("error open read file\n");
exit(-1);
}
FILE* wfp = fopen("whaha", "w");
if(wfp==NULL)
{
printf("error open write file\n");
exit(-1);
}
int i=0;
while(getline(&ss, &len, rfp) != -1)
{
char convss[1024]="";
size_t len;
if((len=convert(cd, ss, convss)) != (size_t)(-1))
fprintf(wfp, "%s", convss);
else
printf("len: %d, line: %d\n", len, ++i);
}
// fprintf(wfp, "%s\n", wo);
iconv_close(cd);
fclose(rfp);
fclose(wfp);
return 0;
}
该代码就可以华丽的将gbk转成utf8.
如果需要在文件之间转换,使用iconv命令即可.常用命令是iconv -f srcCode -t destCode file. fromCode和destCode可以在iconv -l中查到.
背景介绍
vim对编码的支持非常强大,因为它在两个平台都是有无数的人在用,所以vim也是逼不得已.同时,vim的编码支持也非常复杂,鄙人了解的并不多.在这里我只能讲到我懂的一部分,而且惭愧的是我并不能保证我说的都是完全对的.我只是通过实验去验证过.欢迎指错.
首先需要保证vim编译时打开了+multi_byte参数.可以使用vim --version查看
其次,vim的详尽说明可以在vim里查看帮助文档.:help multibyte,里面丰富的介绍了所有与vim有关的编码的知识和细节.我是没看完,大家有兴趣的可以去看看.对了,一定要下中文文档
由于是在linux下,我们只讨字符终端的情况,不讨论gui终端.事实上,后者的问题是前者的子集,而且字符终端使用频率更多.
环境变量vim主要是有三个我们需要关注的变量来控制编码: encoding, fileencoding, fileencodings.如果是字符终端还是有一个termencoding
encoding,vim的官方帮助里是这么说的:
设置 Vim 内部使用的字符编码。它应用于缓冲区、寄存器、表达式所用的字符串、viminfo 保存的等等各种文本。该选项设置 Vim 可以工作的字符类型。文件的字符编码可以和 'encoding' 不同。这由 'fileencoding' 指定。简单的说,这个变量就是vim自己使用的变量,我们不需要特别的关心.帮助文档里一直建议我们把它设成utf-8,所以我们就却之不恭了.注意不要在编辑文件的时候临时设定它的值,一般写在.vimrc里,然后永远都不要理它就行了
fileencoding(fenc), 是设置此缓冲区所在文件的字符编码.如果 'fileencoding' 不同于 'encoding',写文件时需要进行转换.如果 'fileencoding' 为空,使用 'encoding' 相同的值 (而读写文件也不需要转换).简单的说,fileencoding就是当前文件的编码.查看当前文件的编码,可以使用'set fenc'.据我观察,如果是gbk的,fenc显示是enuc-cn.在help multbyte里可以查到,这是vim使用的针对简体中文的一种编码说明.vim里似乎没有gbk的名字,一般使用euc-cn,cp936(windows好像就是这个).我试验了一下,它们都是相同的,主要都是和windows平台对应.
所以,我们可以使用vim来转换文件编码.打开一个文件,设置你的目标编码为fenc,就是fileencoding,然后保存一下就行了.这个好处就是你不需要预先知道你文件的原始编码.当然,你可以不用知道,但vim必须要知道.vim要如何知道你的文件的编码呢,这就要靠下面这个参数
fileencodings(fencs).vim的官方文档解释的非常清楚了:
这是一个字符编码的列表,开始编辑已存在的文件时,参考此选项。如果文件被读入,Vim 尝试使用本列表第一个字符编码。如果检测到错误,使用列表的下一个。如果找到一个能用的编码,设置 'fileencoding' 为该值。如果全都失败,'fileencoding' 设为空字符串,这意味着使用 'encoding' 的值。所以说,要支持各种编码的兄弟们,你们的vimrc里一定要把这个变量给写全了,能写多少写多少,恨不能把所有的全写进去.vim要靠这个列表一个一个猜,虽然不一定保证能猜中,但如果没有那是万万猜不中的.定义这个参数的时候要注意一个问题,就是那些你经常可能会用到的编码尽量写在前面,utf-8最好写在第一个,因为它的识别率最高.vim不能保证一定识别成功,但一般常用的编码格式大多都没有问题.
termencoding(tenc). 这个变量只有要使用终端模式才有用.它的作用就是对当前的终端的编码的一个适配.因为当encoding和终端编码不同的时候,就算vim通过fencs识别出来了该文件,但仍然还是要显示给终端.由于vim内部使用encoding变量,如果二者的编码不同,还是会乱码.当然,我们要是改了encoding就可以了.但因为encoding一般不随便改,所以如果遇到不匹配的终端,可以设置termencoding变量适配.这样vim就可以通过termencoding知道终端是个啥变量了,输出的字符就会重新编码以配合终端.不过,因为我之前建议大家的终端还是设成utf-8的比较好,vim也建议大家的encoding也设成utf-8的比较好,所以一般情况这个termencoding可以不用设.
乱码问题解决一些经常出现的问题.在解决这些问题之前,你需要有两步预备工作:
首先,你要搞明白三个编码:终端编码A,文件编码B,vim的tenc的值C.
其次,你要知道你的vim是否识别这个文件的编码,一般来说我们默认如果你fencs的值D写对了vim就能识别,所以要查看你的vim的fencs有没有包括你的文件编码B
1. 我用vim打开文件乱码,怎么办?
你需要做的是:A∈D, A==C.然后再打开看看?
2. 如何使用vim转换文件编码?
如果你的目标编码是E,你需要使用vim打开文件并且显示正常,并保证E∈D(我觉得一般也没有多少人会有变态的编码需求,连vim都架不住),然后fenc=E,wq保存即可
3. 为何我的文件set enc不显示encoding的值?
哥们,你得看看你的vim --version里,有没有+multi_type.如果前面是个-号的话,重编一个vim吧.我上面说的不知道哪些能用了
4. 为何我的文件使用cat, less等在终端查看的时候会乱码?
你要做到A==B.如果你不想转格式,可以使用vim的tenc适配一下.我暂时没找到shell有适配的功能
5. 为何我的文件在我这台机器上显示正常,在其它机器上显示乱码?
如果你是用vim打开的,请确保保到问题1.如果你是使用终端查看,请设置好自己的终端和文件的编码相同.
的编码 插曲
在做mysql编码的测试时,我遇到一个很头疼的问题.
现在有两台机器,A,B
A上有mysql
当A的locale设置成en_US的话,如果从A上登陆本地mysql,则终端输入中文的时候,显示的是乱码.如果改变A的locale为任何一个其它值,我试了很多,比如export LC_ALL='zh_CN.utf8'如果从A上登陆本地mysql,则终端输入中文的时候没有显示任何东西.就跟没反应一样但从机器B上登陆A的mysql,就能正常.B的locale也是en_US.
两个终端都是UTF8.A的数据库的所有charset都是默认的latin1.新装的还没有动过.
刚才看了一下,好像和终端的版本有关系
A的mysql终端:
mysql> status
--------------
mysql Ver 14.12 Distrib 5.0.45, for unknown-linux-gnu (x86_64) using EditLine wrapper
B的mysql终端
mysql> status
--------------
mysql Ver 14.7 Distrib 4.1.20, for redhat-linux-gnu (x86_64) using readline 4.3
从这里我发现了问题所在.由于机器A上的mysql是我自己用源码编译的,B上的mysql是rpm装的.两个所使用的命令行不同.在mysql的configure选项里可以看到如下一段:
--without-readline Use system readline instead of bundled copy.
--without-libedit Use system libedit instead of bundled copy.
这是mysql可以使用的两种命令行工具.readline是很出名的一个工具,bash,python等都用它.editline这个我倒真没听过.因为我默认没有配置,所以我编出来的mysql客户端是editline的版本.我也不知道为何editline不支持中文或者需要配置什么,但由于readline是我们常用的,我就重新添加了选项编译安装.完成后mysql恢复了正常,版本如下:
mysql> status
--------------
mysql Ver 14.12 Distrib 5.0.45, for unknown-linux-gnu (x86_64) using readline 5.0
这是一段小插曲,但是一个比较难发现的问题,特在此记录一下.
的编码变量
mysql的编码支持列表可以使用命令show character set 显示:
+----------+-----------------------------+---------------------+--------+
| Charset | Description | Default collation | Maxlen |
+----------+-----------------------------+---------------------+--------+
| dec8 | DEC West European | dec8_swedish_ci | 1 |
| cp850 | DOS West European | cp850_general_ci | 1 |
| hp8 | HP West European | hp8_english_ci | 1 |
| koi8r | KOI8-R Relcom Russian | koi8r_general_ci | 1 |
| latin1 | cp1252 West European | latin1_swedish_ci | 1 |
…..
如果其中没有gbk,那就是mysql默认不支持gbk.如果你需要它支持的话,只有重编mysql.在./configure选项with-extra-charset里设置.和vim的设置对比一下,可以看到gbk字符集有各种表示方式,而且大多有区别.但utf8依然是很坚挺的,在每个软件里都是这个名字.所以这也是为什么大多软件都推荐utf8的原因.
Mysql的变量
Mysql有关编码的变量可以使用命令show variables like ‘char%’显示:
+--------------------------+----------------------------------------+
| Variable_name | Value |
+--------------------------+----------------------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql/share/mysql/charsets/ |
+--------------------------+----------------------------------------+
首先,sets_dir这个不用管
Set_system这个是服务器用来保存识别符的字符集,就是存储表名,列名,触发器名的字符集.这个一般都是utf8,而且这个不允许随便改.而且这个也没有必要改,mysql上出现的任何编码问题都跟这个变量无关,所以无视之.
Set_server 是服务器的字符集,它惟一的作用就是为create database提供默认值.它在系统启动的时候可以通过--character-set-server来设置它们。如果没有的话系统会把这两各变量设置成默认值latin1和latin1_swedish_ci。默认值是编译在程序中的,只能通过重新编译来改变。
Set_database 是数据库的字符集,它和上面用处相似,惟一的用处就是用来能create table提供默认值.当它为空的时候就参考set_server
上面的这几个变量,一般都是建服务建库的时候设置,属于非交互性的变量,一般不会随便改.所以记着它们的意思就行了,本节讨论的问题跟他们关系不大.最后
set_client,终端字符集,告诉Server客户端提交的SQL语句的编码格式 .这个变量和vim里的termencoding很像,都是告诉程序你的终端是个什么编码.因为终端的编码是不固定的,不同的人使用不同的终端连接mysql或者使用vim,程序不可能通过一个固定的方法去获取当前连接的终端的编码.所以只有通过这个变量来参考.所以,这个变量需要与当前的终端的编码一致,否则你的sql语句传到服务端的时候就已经是乱码了,出错已不可避免
set_connection连接字符集,是服务器翻译SQL语句时用到的编码格式.比如set_client的编码是A,set_connection的编码是B,那当mysql拿到将客户端的字符串,会做一个从编码A到编码B的转换.然后解析该sql语句.(这个变量我感觉有点多余,因为后面写数据库的时候还是会向数据库的表里的编码做转换,为何不去掉这一步).因此,我们需要保证set_connection的编码C是set_client的编码A可以转换成功的,比如gb2312转gbk,这个没问题.但如果A是utf8,C是latin1,那就会出错.因为latin1是不包含中文字符集的.一般来说,set_connection的值最好保持跟set_client一致,因为多一事不如少一事.反正set_connections只是接收set_client的编码,干嘛一定要转换呢.
表的字符集,可以使用show create table xxx查看.当插入数据时,需要把实现set_connection的编码转换到表的编码,确切的说是与之相关的那个列的编码.这个原理同上面的一样,要保证两个字符集一定是有交集,并且被转换的内容一定是在这个交集之内的!否则存入的将会是乱码.
Set_results 返回的结果集的字符集,是服务器返回结果集之前把结果集转换成的编码格式.这个在读的时候,比如要select的时候,服务端已经生成了一串数据了.它需要把它转成一个格式.转成什么格式呢?就是这个指定的.如果是打在屏幕上,它的值就应该与set_client相同.所以同上,也要保证它们是可转换的.
总的来说,一个字符串从写入mysql到读出,需要经过以下几种变换:
Set_client->set_connection->table character->set_results
好了,我们来看一些例子.每个例子都通过终端写入一个中文字符串,然后读出字符串.
编码试例例子1,所有编码相同
这种情况是最正常的情况,我信设置所有的编码都是gbk.不需要经过任何的一次的编码转换.
变量设置:
mysql> show variables like 'char%';
+--------------------------+----------------------------------------+
| Variable_name | Value |
+--------------------------+----------------------------------------+
| character_set_client | gbk |
| character_set_connection | gbk |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | gbk |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql/share/mysql/charsets/ |
+--------------------------+----------------------------------------+
查看表的编码
mysql> show create table wx.mm;
+-------+-----------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+-----------------------------------------------------------------------------------------+
| mm | CREATE TABLE `mm` (
`name` char(100) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=gbk |
+-------+-----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
写入,读出
mysql> insert into mm values('哈哈');
Query OK, 1 row affected (0.01 sec)
mysql> select * from mm;
+--------+
| name |
+--------+
| 哈哈 |
+--------+
1 row in set (0.00 sec)
例子2,我们将表的编码改成utf8,看看效果:
mysql> alter table mm charset='utf8';
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table mm;
+-------+------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+------------------------------------------------------------------------------------------------------------+
| mm | CREATE TABLE `mm` (
`name` char(100) character set gbk default NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 |
+-------+------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> insert into mm values('哈哈');
Query OK, 1 row affected (0.00 sec)
mysql> select * from mm;
+--------+
| name |
+--------+
| 哈哈 |
+--------+
1 row in set (0.00 sec)
可以看到,仍然可以正常取出写入的值.因为gbk和utf8可以正常转换,在写入时gbk转换成utf8存入表格,读出时通过utf8转换成gbk显示在gbk终端.
例子3.使用与终端不同的set_results编码,我们将set_results设置成utf8
因为我们的终端现在是gbk
mysql> set character_set_results='utf8';
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'char%';
+--------------------------+----------------------------------------+
| Variable_name | Value |
+--------------------------+----------------------------------------+
| character_set_client | gbk |
| character_set_connection | gbk |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql/share/mysql/charsets/ |
+--------------------------+----------------------------------------+
8 rows in set (0.00 sec)
mysql> delete from mm;
Query OK, 1 row affected (0.00 sec)
mysql> insert into mm values('哈哈');
Query OK, 1 row affected (0.00 sec)
mysql> select * from mm;
+-----------+
| name |
+-----------+
| 鍝堝搱 |
+-----------+
1 row in set (0.00 sec)
可以看到,如果set_results的值和终端的值不同时,虽然数据库的值是正确的,但是显示会成乱码.如果把终端的编码改回来就可以正常显示了.如何知道存入的值是正确的呢,可以使用hax函数:
mysql> select hex(name) from mm;
+--------------+
| hex(name) |
+--------------+
| E59388E59388 |
+--------------+
1 row in set (0.00 sec)
mysql> select hex('哈哈') ;
+------------------+
| hex('鍝堝搱') |
+------------------+
| E59388E59388 |
+------------------+
1 row in set (0.00 sec)
hax显示数据的16进制的值.
例子4.所有的变量都是gbk,修改set_connection等于utf8
mysql> delete from mm ;
Query OK, 1 row affected (0.00 sec)
mysql> alter table mm charset='gbk';
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table mm;
+-------+-----------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+-----------------------------------------------------------------------------------------+
| mm | CREATE TABLE `mm` (
`name` char(100) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=gbk |
+-------+-----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> set character_set_connection='utf8';
Query OK, 0 rows affected (0.00 sec)
mysql> insert into mm values('哈哈');
Query OK, 1 row affected (0.00 sec)
mysql> select * from mm;
+--------+
| name |
+--------+
| 哈哈 |
+--------+
1 row in set (0.00 sec)
可以看到,因为utf8和gbk可以相互转换,这次数据流动的编码转换是,set_client的gbk转换成set_connection的utf8,在写入数据库时转换成表的gbk编码,在读出时不需要转换.
例子5.修改set_connection为latin1,该编码与gbk不能相互转换
mysql> set character_set_connection='latin1';
Query OK, 0 rows affected (0.00 sec)
mysql> set character_set_connection='latin1';
Query OK, 0 rows affected (0.00 sec)
mysql> insert mm values('哈哈');
Query OK, 1 row affected (0.00 sec)
mysql> select * from mm;
+------+
| name |
+------+
| ??? |
+------+
1 row in set (0.00 sec)
可以看到,由于gbk和latin1里不能相互转换,或者说latin1不支持任何汉字的编码,所以在从set_client向set_connection转换的时候会出现问题,存在数据库里的编码就是乱码.通过select hex(name) from mm 可以查到存储的值是3F3F3F,这与原来的gbk的值不同.
例子6.设置set_client的值与终端的编码不同
这个例子,我们将set_client设置成utf8,其它的所有保持gbk不变
mysql> set character_set_client='utf8';
Query OK, 0 rows affected (0.00 sec)
mysql> delete from mm;
Query OK, 2 rows affected (0.00 sec)
mysql> select * from mm;
Empty set (0.00 sec)
mysql>insert into mm values('哈哈');
Query OK, 1 row affected (0.00 sec)
mysql> select * from mm;
+------+
| name |
+------+
| 1
+------+
1 row in set (0.00 sec)
可以看到,虽然utf8可以和gbk相互转换,但但是set_client的设置与终端的编码不同,mysql会把一个gbk的sql语句当然utf8转换成set_connection指定的gbk,就是说,本来是个不需要转换的动作做了一次错误的编码转换,所以结果就不可而知.从这个例子可以看出,set_client的值一定要与终端的编码一样.
例子7 使用mysql导出结果.设定set_results为gbk
现在我们有如下一张表:
mysql> show create table mm;
+-------+-----------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+-----------------------------------------------------------------------------------------+
| mm | CREATE TABLE `mm` (
`name` char(100) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=gbk |
+-------+-----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
里面内容如下:
mysql> select * from mm;
+--------+
| name |
+--------+
| 哈哈 |
| 吼吼吼 |
+--------+
我们执行两个sql语句并将结果重定向到文件里:
[mysql@10.81.52.135]~>$ mysql -e 'set character_set_results=utf8; select * from wx.mm' > utf8.res
[mysql@10.81.52.135]~>$ mysql -e 'set character_set_results=gbk; select * from wx.mm' > gbk.res
[mysql@10.81.52.135]~>$ file *.res
gbk.res: ISO-8859 text
utf8.res: UTF-8 Unicode text
如果用vim打开,可以正常查看两个文件的内容,但两个文件的编码是不同的.这是因为set_results变量的原因.这个例子可以很好的诠释set_results变量的作用.
综上所述,我们做了7个例子,总结一下,可以得出以下结论:
1. Set_client一定要与当前终端的编码相同,否则在使用包含中文字符的sql语句时不可能正常.
2. Set_results的值是输出的结果的编码,如果是输出到终端上,也需要设为与当前终端的编码相同.如果重定向并且重定向结果是正常的,则重定向的文件编码以set_results决定.
3. Set_connection需要与set_client和指定编辑的表的编码可以相互转换.一般来说,在纯中文环境下,utf8和gbk是可以相互转换并不会丢失或截断.
4. 一般情况下,建议把表,数据库,服务和set_connection的编码全都设为utf8.虽然在存储中文的情况下utf8会更占空间,但为这个解决乱码问题是值得的.set_client和set_results的值以环境设置.由于这前我们都建议使用utf8的终端,set_client的值也应该设为utf8..这是一种很理想的mysql的编码方式.