关于字符串编码及其正确IO的各种讨论

  这是个困扰我将近2年的问题,最近稍微积累了点经验,在这里记录一下,作为他人和自己日后的参考。当然,也不保证经验都是正确的。

  首先,字符集和字符编码完全是两个概念。字符集关心的是字符的个数、有哪些字符、字符排列的顺序以及字符集的编码范围等。UNICODE字符集是一套唯一的包含这个星球上一切称得上语言文字的东西,除此之外其他的字符集都可以认为是方言,包含不完全的文字集合,并且与UNICODE不兼容。当然ASCII字符集由于其历史优势,大家默认对其保持不变,提供形式上的兼容(因为这取决于编码方式的实现)。字符编码是对某种特定字符集的计算机表示规则,由于一套字符编码所表示的字符是固定的,也可以看成一张文字和计算机编码的对照表。同一套字符集可以有多套编码,如UNICODE有UTF-8、UTF-16、UTF-32等编码方式,这些方式的优缺点比较我就不罗嗦了。总之现在的国际趋势是UTF-8。中文常见的字符集有GB2312、GBK、BIG5等,GB2312是国标,GBK完全兼容GB2312并做了扩充以支持生僻字和繁体字,BIG5是台湾的繁体字符集。GBK的中文编码采用两个字节,英文编码采用一个字节。UTF-8的中文采用三个字节,英文采用一个字节,并且与CPU字节序无关(唯一一种字节序无关编码)。所以中文较多的时候国标文件小一点。

  另外,系统的语言包是用来实现“本地化”的字符集,为什么有了UNICODE还要安装中文语言包呢,因为我国流行的字符集不是UNICODE。所以尽管OS可以正确的显示“我的文档”,但没有GBXXX字符集,就无法显示“百度一下”,也没法打开使用GB2312编写的文档,无法展示GB2312编码的中文界面。所以,程序员使用UNICODE会避免绝大部分界面展示乱码问题,所有的操作系统都支持UNICODE。Windows内核就是UNICODE字符集并采用UTF-16编码的,所有涉及到字符串的API都首先支持W版,使用A版会降低效率。话说回来,Windows选择UTF-16实在是一大败笔,因为这种编码方式会用两个字节表示一个字符,直接导致ANSI C函数库的大量函数失效。因为C-stirng是以0结尾的,UTF-16中的前255个字符都是以一个多余的0作为高位。所以我们处理Windows上的宽字符串几乎是以WindowsAPI为主。

  说到Windows的窄字符集,也叫ANSI字符集,是一个泛指。不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。也就是说ANSI代表哪种编码取决于Windows系统的locale设置,即默认编码方式。

  作为一个程序员,想和字符串打交道,第一关便是程序的源文件。本来以为是输入法,其实不是,输入法采用什么样的字符集和编码方式和系统当前的locale相同。源文件的编码是和系统locale相同的。每一种编辑器都有其默认的字符集和编码方式,一般都和系统的默认值保持一致以保证输入的时候能看到正确的回显。对中文Windows系统,这个字符集是GBK,英文版没用过也不清楚。Linux系统则默认是UNICODE字符集的UTF-8编码,好像还是不带开始标志的那种。我们还可以为编辑器指定字符集,但是这个字符集只有显示意义,在非locale字符集的编码下输入是得不到正确回显的,也就不能把文件保存成其他字符集。当然这和编辑器的努力也是有关系的,前面说的是vi的处理方式。其实编辑器完全可以设计成用什么编码方式显示就用什么编码方式保存和回显。字符集为了互相区别也稍微做了些努力,比如BOM标志符号。Windows就是使用BOM来标记文本文件的编码方式的。标记的另一个用处是区分编码被big-endian还是little-endian保存。

  源文件中的const字符串会被编译到目标文件,最终成为可执行文件的一部分。所以为了得到正确的显示结果,必须保证源文件的locale编码和执行环境的locale一致。为了达到这个目的,我们可以在程序中指定执行环境的locale为某种编码。为了保证执行环境支持我们的设定,应该使用UNICODE字符集。考虑到操作系统的实现,Windows下使用UTF-16编码效率会高,且各版本编码兼容,不会出现显示乱码的情况。而使用UTF-16也不必把locale设定为UTF-16,Windows已经考虑的很周到了,我们可以用L"某文字"的方式让编译器转换编码。在Linux下,由于默认locale是UTF-8,那也不差,只不过要保证源文件不是来自Windows默认编辑的就可以了。关键是交叉编译的时候,如果在Windows下编辑又要编译为Linux程序,那么源文件一定要转换编码,或者是为Linux指定运行时locale。

  最后一种情况是Windows下的程序生成要在Linux下展示的文件,或者反过来。这基本上是socket的功劳。这种情况下除了很丑陋的修改源文件某些部分的文字编码外,还可以自己进行编码转换。这项工作我借用libiconv库。。。没搞定。

  还有一个问题没解决:Linux下设定locale为GB2312,源文件使用GB2312编码转换,使用fopen("中文名","a");生成文件名乱码!!而且内部文件内容的中文完全正常,是GB2312编码。文件名在Windows下是没有问题的。路过的大神能给个令人震惊的解释不~0~

  ====以此为背景割了他妹的GB2312==========

  昨天的问题是因为,我设置Linux下的locale为GB2312之后没有重启系统,仅仅重启了终端。。重启之后,可以生成正常的文件名了。但是虚拟机不能把这个文件copy出来,这个原因我暂时认为是虚拟机的问题。另外,生成的文件通过sftp上传到Windows下的服务器上,文件名再次乱码!而且乱得似曾相识!分明就是了linux下UTF-8编码时生成GB2312文件名的样子!也就是说,既然Linux下能找到正确的文件,上传能成功,Linux下的工作就没有问题。出问题的既不是Linux客户端也不是sftp上传的过程,只能是Windows服务端——虽然Windows是GB2312编码的,但我用的模拟服务器未必就支持GB2312,如果是仅支持UTF-8就会出现这种乱码情况!

  打开MobaSSH配置文件,果然有所斩获:

  修改编码为zh_CN.GB2312,保存并重启服务。。。

  好吧其实文件名仍然乱码。。。

  结贴!

posted @ 2013-01-11 01:49  mjwk  阅读(636)  评论(0编辑  收藏  举报