计算机中的字符串编码、乱码、BOM等问题详解

因为电脑是windows 7系统,开发环境又在linux,经常在linux碰到乱码问题,很是痛苦,于是决定好好了解编码的来龙气脉,并分享个各位,免得出现乱码时不知所措。

是否存在文件编码

在讲解字符编码之前,我们需先明确文件本身没有编码一说,只有文字才有编码的概念,我们通常说某个文件是什么编码,通常是指文件里字符的编码。

vim为什么会出现乱码

我在linux下一般使用vim进行文件编辑,发现经常会碰到乱码的情况,那么为什么会出现乱码呢? 首先我们了解下vim编码方面的基础知识,关于编码方面vim存在3个变量:

1.encoding: Vim 内部使用的字符编码方式,包括 Vim 的 buffer (缓冲区)、菜单文本、消息文本等。默认是根据你的locale选择.用户手册上建议只在 .vimrc 中改变它的值,事实上似乎也只有在.vimrc 中改变它的值才有意义。你可以用另外一种编码来编辑和保存文件,如你的vim的encoding为utf-8,所编辑的文件采用cp936编码,vim会自动将读入的文件转成utf-8(vim的能读懂的方式),而当你写入文件时,又会自动转回成cp936(文件的保存编码).

2.fileencoding: Vim 中当前编辑的文件的字符编码方式,Vim 保存文件时也会将文件保存为这种字符编码方式 (不管是否新文件都如此)。

3.termencoding: Vim 所工作的终端 (或者 Windows 的 Console 窗口) 的字符编码方式。如果vim所在的term与vim编码相同,则无需设置。如其不然,你可以用vim的termencoding选项将自动转换成term的编码.

当这三个变量的编码出现问题时就会出现乱码:

1.encoding不是utf-8编码:如果encoding不是utf-8编码,其他字符可能无法转为 Encoding的指定编码。如:如果encoding是gbk编码,而文件内容采用big5编码的,其就无法转换为gbk编码。

2.fileencoding不对:Fileencoding编码是vim的读取文件内容时使用的编码,如果其编码与文件字符编码不同,必然会出现乱码。Fileencoding编码一般由vim自动检测,可以使用fileencodings设置,Vim自动探测fileencoding的顺序列表。不过vim有时候会检测错误,就会出现乱码了。

3.termencoding编码不对:我们登录服务器一般采用远程登录,这就涉及终端编码问题,我经常碰到因为终端编码不对导致乱码的。如:SecureCRT设置为utf-8编码,而vim的termencoding却为gbk,就出现乱码了。

字符编码介绍

说了那么多,到底字符编码是什么玩意呢?

字符(Character)是文字与符号的总称,包括文字、图形符号、数学符号等,一组抽象字符的集合就是字符集(Charset)。 字符集常常和一种具体的语言文字对应起来,该文字中的所有字符或者大部分常用字符就构成了该文字的字符集,比如英文字符集,汉字字符集。 计算机要处理各种字符,就需要将字符和二进制内码对应起来,这种对应关系就是字符编码(Encoding)。 制定编码首先要确定字符集,并将字符集内的字符排序,然后和二进制数字对应起来。根据字符集内字符的多少,会确定用几个字节来编码。

字符集和编码的区别:

字符集字符的集合,不一定适合计算机存储、网络传送、处理,有时须经编码(encode)后才能应用。如Unicode字符集可依不同需要以UTF-8、UTF-16、UTF-32等方式编码。

字符编码的发展

字符编码大概分为三个发展阶段。

ASCII编码:ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)是基于拉丁字母的一套电脑编码系统。 因为计算机起源于美国,为表示英文字符,他们制定了ASCII编码,ASCII编码使用7位二进制来表示字符,高位用来做奇偶校验,0×20以下的字节状态称为”控制码”,同时包括标点符号、数字等,当时人们觉得已经够用了。

多种编码并存:但随着计算机的广泛应用,别的国家也开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的,为了可以在计算机保存他们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128到255这一页的字符集被称”扩展字符集”。最优秀的扩展方案是ISO 8859-1,通常称之为Latin-1,Latin-1包括了足够的附加字符集来写基本的西欧语言。

后来其他国家也开始使用计算机,为了表达自己国家的文字,不同的国家和地区制定了不同的标准,产生了GB2312,BIG5,JIS等各自的编码标准。通常使用0×80~xFF范围的2个字节来表示1个字符。比如:汉字 ‘中' 在中文操作系统中,使用 [0xD6,0xD0]这两个字节存储。 这些使用2个字节来代表一个字符的延伸编码方式,称为 ANSI 编码。

在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。 不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。

Unicode编码 由于不同国家的ANSI编码互不兼容,为了使国际间信息交流更加方便,国际组织制定了UNICODE字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。 UNICODE开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是ISO就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ascii里的那些“半角”字符,UNICODE保持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于”半角”英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。

但是UNICODE来到时,一起到来的还有计算机网络的兴起,UNICODE如何在网络上传输也是一个必须考虑的问题,于是面向传输的众UTF(UCS Transfer Format)标准出现了,顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从UNICODE到UTF时并不是直接的对应,而是要过一些算法和规则来转换。

学过计算机网络的童鞋都知道,在网络里传递信息时有一个很重要的问题,就是对于数据高低位的解读方式,一些计算机是采用低位先发送的方法,例如我们PC机采用的INTEL架构;而另一些是采用高位先发送的方式。在网络中交换数据时,为了核对双方对于高低位的认识是否是一致的,采用了一种很简便的方法,就是在文本流的开始时向对方发送一个标志符——如果之后的文本是高位先发送,那就发送”FEFF”,反之,则发送”FFFE”。(IP/TCP协议规定网络字节序使用大端法)
因为UTF-8编码兼容之前的ASCII编码,而且有利于传输,其得到了非常广泛的应用。

从UNICODE到UTF8的转换规则:

复制代码 代码如下:

Unicode             UTF-8
0000 - 007F         0xxxxxxx
0080 - 07FF         110xxxxx 10xxxxxx
0800 - FFFF         1110xxxx 10xxxxxx 10xxxxxx

汉字ANSI编码发展

GB2312:为了表示汉字,我们国家首先发明了GB2312 编码,GB2312 编码规定一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。

GBK 后来发现很多偏僻字和少数名族的语言还是没办法编码进来,为了表示这些字符,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK包括了GB2312的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。

GB18030 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK扩成了GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。

BOM介绍

1.BOM的来历: 为了识别 Unicode 文件,Microsoft 建议所有的 Unicode 文件应该以 ZERO WIDTH NOBREAK SPACE(U+FEFF)字符开头。这作为一个“特征符”或“字节顺序标记(byte-order mark,BOM)”来识别文件中使用的编码和字节顺序。

2.不同的系统对BOM的支持:因为一些系统或程序不支持BOM,因此带有BOM的Unicode文件有时会带来一些问题。

①JDK1.5以及之前的Reader都不能处理带有BOM的UTF-8编码的文件,解析这种格式的xml文件时,会抛出异常:Content is not allowed in prolog。
②Linux/UNIX 并没有使用 BOM,因为它会破坏现有的 ASCII 文件的语法约定。
③不同的编辑工具对BOM的处理也各不相同。使用Windows自带的记事本将文件保存为UTF-8编码的时候,记事本会自动在文件开头插入BOM(虽然BOM对UTF-8来说并不是必须的)。而其它很多编辑器用不用BOM是可以选择的。UTF-8、UTF-16都是如此。

3.BOM与XML:XML解析读取XML文档时,W3C定义了3条规则:

①如果文档中有BOM,就定义了文件编码;
②如果文档中没有BOM,就查看XML声明中的编码属性;
③如果上述两者都没有,就假定XML文档采用UTF-8编码。

决定文本的字符集与编码

软件通常有三种途径来决定文本的字符集和编码:

1.检测BOM

对于Unicode文本最标准的途径是检测文本最开头的几个字节。   

复制代码 代码如下:

    开头字节        Charset/encoding
    EF BB BF        UTF-8
    FE FF           UTF-16/UCS-2, little endian(UTF-16LE)
    FF FE           UTF-16/UCS-2, big endian(UTF-16BE)
    FF FE 00 00     UTF-32/UCS-4, little endian.
    00 00 FE FF     UTF-32/UCS-4, big-endia

2.用户选择: 采取一种比较安全的方式来决定字符集及其编码,那就是弹出一个对话框来请示用户。

3.采取“猜”的方法: 如果软件不想麻烦用户,或者它不方便向用户请示,那它只能采取自己“猜”的方法,软件可以根据整个文本的特征来猜测它可能属于哪个charset,这就很可能不准了。使用记事本打开那个“联通”文件就属于这种情况。(把原本属于ANSI编码的文件当成UTF-8处理。

记事本的几种编码介绍

1.ANSI编码:记事本默认保存的编码格式是:ANSI,即本地操作系统默认的内码,简体中文一般为GB2312。这个怎么验证呢?用记事本保存后,使用EmEditor、EditPlus和UltraEdit之类的文本编辑器打开。推荐使用EmEditor,打开后,在又下角会显示编码:GB2312。

2.Unicode编码:用记事本另存为时,编码选择“Unicode”,用EmEditor打开该文件,发现编码格式是:UTF-16LE+BOM(有签名)。用十六进制方式查看,发现开头两字节为:FF FE。这就是BOM。

3.Unicode big endian 用记事本另存为时,编码选择“Unicode”,用EmEditor打开该文件,发现编码格式是:UTF-16BE+BOM(有签名)。用十六进制方式查看,发现开头两字节为:FE FF。这就是BOM。

4.UTF-8 :用记事本另存为时,编码选择“UTF-8”,用EmEditor打开该文件,发现编码格式是:UTF-8(有签名)。用十六进制方式查看,发现开头三个字节为:EF BB BF。这就是BOM。