开发过程中,使用文本文档、Word文档过程中,经常会遇到乱码,这些乱码是怎么产生的?
字符存储都与哪些因素有关?
经常听到的字符集、字符编码是什么?文件存储编码是什么?
Windows记事本另存为时可以选择:ANSI、Unicode、Unicode Big Ending、UTF-8,分别表示什么?如何转换?Windows系统如何区分一个txt文档是什么编码的?
基本概念
- 字符(Character) 是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
- 字符集(Character set) 是一个系统支持的所有抽象字符的集合。通常以二维表的形式存在,二维表的内容和大小是由使用者的语言而定。如ASCII,GBxxx,Unicode等。
- 字符编码(Character encoding) 是把字符集中的字符编码为特定的二进制数,以便在计算机中存储。每个字符集中的字符都对应一个唯一的二进制编码。
字符集和字符编码一般都是成对出现的,如ASCII、IOS-8859-1、GB2312、GBK,都是即表示了字符集又表示了对应的字符编码。Unicode比较特殊,有多种字符编码(UTF-8,UTF-16等)
ASCII单字节-->ANSI多字节-->UNICODE宽字节
二进制
机器码
字符集:一种或多种语言中字符的集合,每个字符有个字符编号,字符集:ASCII、Unicode、GB2312、GBK
字符编号:字符在字符集中的索引号,ANSI、Unicode(UCS-2、UCS-4)
字符编码(即存储编码):在计算机内存中的编码格式,由字符编号转化而来,如UTF-8、UTF-16、UTF-32
Unicode:包含全球各种语言累计百万字符的字符集,给全球字符设定规则并排了个序
UTF8:变长字符编码,单字节表示ASCII码,中文一般用3字节表示
GB2312:《信息交换用汉字编码字符集》国标2312-1980,基本集共收入汉字6763个和非汉字图形字符682个。整个字符集分成94个区,每区有94个位。每个区位上只有一个字符,因此可用所在的区和位来对汉字进行编码,称为区位码。把换算成十六进制的区位码加上2020H,就得到国标码。国标码加上8080H,就得到常用的计算机机内码。
GBK:全称《汉字内码扩展规范》(GBK即“国标”、“扩展”汉语拼音的第一个字母,GBK 向下与 GB 2312 编码兼容,向上支持 ISO 10646.1国际标准,
ASCII:美国制定的一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。ASCII 码一共规定了128个字符的编码
从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。
Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。 编码字符集的标准有两个ANSI和Unicode,ANSI每个国家有自己的实现,其编码与使用区域挂钩。unicode为表示现在已知的任何符号,但是为了传输和存储方便,产生了不同的实现方式utf-8,utf-16,utf-32。
Unicode 可以使用的编码有三种:
UFT-8:一种变长的编码方案,使用 1~6 个字节来存储;
UFT-32:一种固定长度的编码方案,不管字符编号大小,始终使用 4 个字节来存储;
UTF-16:介于 UTF-8 和 UTF-32 之间,使用 2 个或者 4 个字节来存储,长度既固定又可变。
UTF 是 Unicode Transformation Format 的缩写,意思是“Unicode转换格式”,后面的数字表明至少使用多少个比特位(Bit)来存储字符。UCS规定了怎么用多个字节表示各种文字。而由UTF(UCS Transformation Format)规范规定怎样传输存储这些编码,是,常见的UTF规范包括UTF-8、UTF-7、UTF-16。
1) UTF-8
UTF-8 的编码规则很简单:如果只有一个字节,那么最高的比特位为 0;如果有多个字节,那么第一个字节从最高位开始,连续有几个比特位的值为 1,就使用几个字节编码,剩下的字节均以 10 开头。
具体的表现形式为:
0xxxxxxx:单字节编码形式,这和 ASCII 编码完全一样,因此 UTF-8 是兼容 ASCII 的;
110xxxxx 10xxxxxx:双字节编码形式;
1110xxxx 10xxxxxx 10xxxxxx:三字节编码形式;
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字节编码形式。
xxx 就用来存储 Unicode 中的字符编号。字符编号与编码之间就有了映射关系。
下面是一些字符的编码实例(绿色部分表示本来的 Unicode 编号):
字符 N æ ⻬
Unicode 编号(二进制) 01001110 11100110 00101110 11101100
Unicode 编号(十六进制) 4E E6 2E EC
UTF-8 编码(二进制) 01001110 11000011 10100110 11100010 10111011 10101100
UTF-8 编码(十六进制) 4E C3 A6 E2 BB AC
对于常用的字符,它的 Unicode 编号范围是 0 ~ FFFF,用 1~3 个字节足以存储,只有及其罕见,或者只有少数地区使用的字符才需要 4~6个字节存储。
2) UTF-32
UTF-32 是固定长度的编码,始终占用 4 个字节,足以容纳所有的 Unicode 字符,所以直接存储 Unicode 编号即可,不需要任何编码转换。浪费了空间,提高了效率。
3) UTF-16
UFT-16 比较奇葩,它使用 2 个或者 4 个字节来存储。
对于 Unicode 编号范围在 0 ~ FFFF 之间的字符,UTF-16 使用两个字节存储,并且直接存储 Unicode 编号,不用进行编码转换,这跟 UTF-32 非常类似。
对于 Unicode 编号范围在 10000~10FFFF 之间的字符,UTF-16 使用四个字节存储,具体来说就是:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于 D800~DBFF 之间的双字节存储,较低的一些比特位(剩下的比特位)用一个值介于 DC00~DFFF 之间的双字节存储。
如果你不理解什么意思,请看下面的表格:
Unicode 编号范围
(十六进制) 具体的 Unicode 编号
(二进制) UTF-16 编码 编码后的
字节数
0000 0000 ~ 0000 FFFF xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx 2
0001 0000---0010 FFFF yyyy yyyy yyxx xxxx xxxx 110110yy yyyyyyyy 110111xx xxxxxxxx 4
位于 D800~0xDFFF 之间的 Unicode 编码是特别为四字节的 UTF-16 编码预留的,所以不应该在这个范围内指定任何字符。如果你真的去查看 Unicode 字符集,会发现这个区间内确实没有收录任何字符。
UTF-16 要求在制定 Unicode 字符集时必须考虑到编码问题,所以真正的 Unicode 字符集也不是随意编排字符的。
总结
只有 UTF-8 兼容 ASCII,UTF-32 和 UTF-16 都不兼容 ASCII,因为它们没有单字节编码。
GB2312、GBK、Shift-JIS 等特定国家的字符集都是在 ASCII 的基础上发展起来的,它们都兼容 ASCII,所以只能采用变长的编码方案:用一个字节存储 ASCII 字符,用多个字节存储本国字符。
以 GB2312 为例,该字符集收录的字符较少,所以使用 1~2 个字节编码。
对于 ASCII 字符,使用一个字节存储,并且该字节的最高位是 0;
对于中国的字符,使用两个字节存储,并且规定每个字节的最高位都是 1。
由于单字节和双字节的最高位不一样,所以很容易区分一个字符到底用了几个字节。
一般情况下用无BOM的形式吧,除非有问题的时候,再考虑换有BOM的。Windows系统保存的都是有BOM的,所以你可以看到,用记事本保存一个UTF-8的txt,其实是有BOM的,这一点需要注意。另外不同的文本编辑器对于有无BOM的称呼也略有不同,比如EditPlus,有BOM的称为UTF-8+,无BOM的称为UTF-8,而在Notepad++中,有BOM的被称为标准UTF-8,而无BOM则被称为UTF-8无BOM。
UTF-8编码的文本文档,有的带有BOM (Byte Order Mark, 字节序标志),即0xEF, 0xBB, 0xBF,有的没有。Windows下的txt文本编辑器在保存UTF-8格式的文本文档时会自动添加BOM到文件头。在判断这类文档时,可以根据文档的前3个字节来进行判断。然而BOM不是必需的,而且也不是推荐的。对不希望UTF-8文档带有BOM的程序会带来兼容性问题,例如Java编译器在编译带有BOM的UTF-8源文件时就会出错。而且BOM去掉了UTF-8一个期望的特性,即是在文本全部是ASCII字符时UTF-8是和ASCII一致的,即UTF-8向下兼容ASCII。
在具体判断时,如果文档不带有BOM,就无法根据BOM做出判断,而且IsTextUnicode API也无法对UTF-8编码的Unicode字符串做出判断。那在编程判断时就要根据UTF-8字符编码的规律进行判断了。
BOM,英文名是ByteOrderMark,用它来表示当前这个文本格式,其对应的关系如下(图来自于百度百科)。
ANSI 的编码格式为什么可以显示中文?在简体中文系统下,ANSI 编码代表 GB2312 编码,是计算机可以识别的编码,适用于汉字处理、汉字通信等系统之间的信息交换。汉字的Ansi是这样的,用两个ANSI码来表示一个汉字,这时第一个最高位为标志位,当为1的时候就认为当前ANSI码和后边紧接的一个ANSI码两个组合起来来表示一个汉字.而普通的Ansi,每个编码高位都是0。所以,如果遇到1开头的,软件就知道,它与后面的Ansi组成了一个汉字
ANSI编码 (本地化) | 为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 '中' 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。 |
ANSI 确实是遗留编码,在不同语言的系统中编码不同,这一部分在微软的术语中叫 code page。比如所谓 GBK 编码,实际上更多地被叫做 CP936。
其实ANSI并不是某一种特定的字符编码,而是在不同的系统中,ANSI表示不同的编码。你的美国同事Bob的系统中ANSI编码其实是ASCII编码(ASCII编码不能表示汉字,所以汉字为乱码),而你的系统中(“汉字”正常显示)ANSI编码其实是GBK编码,而韩文系统中(“한국어”正常显示)ANSI编码其实是EUC-KR编码。
那么Windows系统是如何区分ANSI背后的真实编码的呢?
微软用一个叫“Windows code pages”(在命令行下执行chcp命令可以查看当前code page的值)的值来判断系统默认编码,比如:简体中文的code page值为936(它表示GBK编码,win95之前表示GB2312,详见:Microsoft Windows' Code Page 936),繁体中文的code page值为950(表示Big-5编码)。
我们能否通过修改Windows code pages的值来改变“ANSI编码”呢?命令提示符下,我们可以通过chcp命令来修改当前终端的active code page,例如:
(1) 执行:chcp 437,code page改为437,当前终端的默认编码就为ASCII编码了(汉字就成乱码了);
(2) 执行:chcp 936,code page改为936,当前终端的默认编码就为GBK编码了(汉字又能正常显示了)。
上面的操作只在当前终端起作用,并不会影响系统默认的“ANSI编码”。(更改命令行默认codepage参看:设置cmd的codepage的方法)。
Windows下code page是根据当前系统区域(locale)来设置的,要想修改系统默认的“ANSI编码”,我们可以通过修改系统区域来实现(“控制面板” =>“时钟、语言和区域”=>“区域和语言”=>“管理”=>“更改系统区域设置...”)
如何判断一个文本文件是无BOM 的UTF8 编码 还是 ANSI编码?
也就是如何区分宽字节字符串与多字节字符串?
关于GBK编码的BUG
很多细心的人会发现,新建一个空的文本文件,用记事本打开(必须是Windows自带的记事本),只输入“联通”二字保存关闭(输入“1联通”也是联通显示的也是乱码),再重新打开时将是乱码。当txt文档中一切字符都在 C0≤AA(第一个字节)≤DF 80≤BB(第二个字节)≤BF 这个范围时,notepad都无法确认文档的格式,自动依照GB-2312来解码。 而"联通"就是C1 AA CD A8,刚好在上面的范围内,所以不能正常显现。记事本默认是以ANSI编码保存文本文档的,而正是这种编码存在的bug招致了上述怪现象。假如保存时选择Unicode、Unicode (Big Endian)、UTF-8编码,就正常了。
Windows记事本要支持Unicode,但是有一个问题,一段二进制编码,如何确定它是GBK还是BIG5还是UTF-16/UTF-8?
记事本的做法是在TXT文件的最前面保存一个标签,如果记事本打开一个TXT,发现这个标签,就说明是unicode。标签叫BOM,如果是0xFF 0xFE,是UTF16LE,如果是0xFE 0xFF则UTF16BE,如果是0xEF 0xBB 0xBF,则是UTF-8。如果没有这三个东西,那么就是ANSI,使用操作系统的默认语言编码来解释。Unicode的好处就是,不论你的TXT放到什么语言版本的Windows上,都能正常显示。而ANSI编码则不能。
参考资料:
http://www.regexlab.com/zh/encoding.htm
https://pcedu.pconline.com.cn/empolder/gj/other/0505/616631.html
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html