`
jiagou
  • 浏览: 2516483 次
文章分类
社区版块
存档分类
最新评论

windows下的中文文件名共享在linux下显示乱码的问题

 
阅读更多
1.现象描述
在windos环境下有一个含有中文的文件名,比如dataset_省调.scd。
在linux(虚拟机)下通过挂载系统将该文件挂载在/mnt/hgfs目录下,显示为乱码。但是通过ftp将文件上传到linux相应目录下中文显示正常。

2.原因分析
windows下的文件名编码方式为GBK。linux(虚拟机)系统下通过挂载方式共享该文件时,文件名的中文自动自动转换为了UTF-8编码方式,在linux系统下中文编码设置为GBK编码方式,所以显示为乱码。

3.验证
使用编码转换工具,将查看“省调”两个字的GBK编码为CA A1 B5 F7,UTF8编码为E7 9C 81 E8 B0 83,显示为”鐪佽皟“。该汉字在linux系1.现象描述
在windos环境下有一个含有中文的文件名,比如dataset_省调.scd。
在linux(虚拟机)下通过挂载系统将该文件挂载在/mnt/hgfs目录下,显示为乱码。但是通过ftp将文件上传到linux相应目录下中文显示正常。

2.原因分析
windows下的文件名编码方式为GBK。linux(虚拟机)系统下通过挂载方式共享该文件时,文件名的中文自动自动转换为了UTF-8编码方式,在linux系统下中文编码设置为GBK编码方式,所以显示为乱码。

3.验证
使用编码转换工具,将查看“省调”两个字的GBK编码为CA A1 B5 F7,UTF8编码为E7 9C 81 E8 B0 83,显示为”鐪佽皟“。该汉字在linux系统下显示正好为上述编码的三个乱码汉字。

4.问题解决
可以通过FTP或其它一些同步工具将该文件上传到linux系统下。

5.知识拓展
字符(Character)是文字与符号的总称,包括文字、图形符号、数学符号等。一组抽象字符的集合就是字符集(Charset)。
计算机要处理各种字符,就需要将字符和二进制内码对应起来,这种对应关系就是字符编码(Encoding)。
制定编码首先要确定字符集,并将字符集内的字符排序,然后和二进制数字对应起来。根据字符集内字符的多少,会确定用几个字节来编码。
每种编码都限定了一个明确的字符集合,叫做被编码过的字符集(Coded Character Set),这是字符集的另外一个含义。通常所说的字符集大多是这个含义。

常用的字符集有:ASCII、ISO 8859-1、UCS、Unicode、UTF、汉字编码、ANSI
下面是转的各种编码方式的详细介绍:
ASCII:
American Standard Code for Information Interchange,美国信息交换标准码。
目前计算机中用得最广泛的字符集及其编码,由美国国家标准局(ANSI)制定。
它已被国际标准化组织(ISO)定为国际标准,称为ISO 646标准。
ASCII字符集由控制字符和图形字符组成。
在计算机的存储单元中,一个ASCII码值占一个字节(8个二进制位),其最高位(b7)用作奇偶校验位。
所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。
奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1。
偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。

ISO 8859-1:
ISO 8859,全称ISO/IEC 8859,是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准,现时定义了15个字符集。
ASCII收录了空格及94个“可印刷字符”,足以给英语使用。
但是,其他使用拉丁字母的语言(主要是欧洲国家的语言),都有一定数量的变音字母,故可以使用ASCII及控制字符以外的区域来储存及表示。
除了使用拉丁字母的语言外,使用西里尔字母的东欧语言、希腊语、泰语、现代阿拉伯语、希伯来语等,都可以使用这个形式来储存及表示。
* ISO 8859-1 (Latin-1) - 西欧语言
* ISO 8859-2 (Latin-2) - 中欧语言
* ISO 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。
* ISO 8859-4 (Latin-4) - 北欧语言
* ISO 8859-5 (Cyrillic) - 斯拉夫语言
* ISO 8859-6 (Arabic) - 阿拉伯语
* ISO 8859-7 (Greek) - 希腊语
* ISO 8859-8 (Hebrew) - 希伯来语(视觉顺序)
* ISO 8859-8-I - 希伯来语(逻辑顺序)
* ISO 8859-9 (Latin-5 或 Turkish) - 它把Latin-1的冰岛语字母换走,加入土耳其语字母。
* ISO 8859-10 (Latin-6 或 Nordic) - 北日耳曼语支,用来代替Latin-4。
* ISO 8859-11 (Thai) - 泰语,从泰国的 TIS620 标准字集演化而来。
* ISO 8859-13 (Latin-7 或 Baltic Rim) - 波罗的语族
* ISO 8859-14 (Latin-8 或 Celtic) - 凯尔特语族
* ISO 8859-15 (Latin-9) - 西欧语言,加入Latin-1欠缺的法语及芬兰语重音字母,以及欧元符号。
* ISO 8859-16 (Latin-10) - 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号。
很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。
但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。
而且在很多协议上,默认使用该编码。

UCS:
通用字符集(Universal Character Set,UCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的字符编码方式,采用4字节编码。
UCS包含了已知语言的所有字符。
除了拉丁语、希腊语、斯拉夫语、希伯来语、阿拉伯语、亚美尼亚语、格鲁吉亚语,还包括中文、日文、韩文这样的象形文字,UCS还包括大量的图形、印刷、数学、科学符号。
* UCS-2: 与unicode的2byte编码基本一样。
* UCS-4: 4byte编码, 目前是在UCS-2前加上2个全零的byte。

Unicode:
Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。
它是http://www.unicode.org制定的编码机制, 要将全世界常用文字都函括进去。
它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。
但自从unicode2.0开始,unicode采用了与ISO 10646-1相同的字库和字码,ISO也承诺ISO10646将不会给超出0x10FFFF的UCS-4编码赋值,使得两者保持一致。
Unicode的编码方式与ISO 10646的通用字符集(Universal Character Set,UCS)概念相对应,目前的用于实用的Unicode版本对应于UCS-2,使用16位的编码空间。
也就是每个字符占用2个字节,基本满足各种语言的使用。实际上目前版本的Unicode尚未填充满这16位编码,保留了大量空间作为特殊使用或将来扩展。

UTF:
Unicode 的实现方式不同于编码方式。
一个字符的Unicode编码是确定的,但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。
Unicode的实现方式称为Unicode转换格式(Unicode Translation Format,简称为 UTF)。
* UTF-8: 8bit变长编码,对于大多数常用字符集(ASCII中0~127字符)它只使用单字节,而对其它常用字符(特别是朝鲜和汉语会意文字),它使用3字节。
* UTF-16: 16bit编码,是变长码,大致相当于20位编码,值在0到0x10FFFF之间,基本上就是unicode编码的实现,与CPU字序有关。


汉字编码:
* GB2312字集是简体字集,全称为GB2312(80)字集,共包括国标简体汉字6763个。
* BIG5字集是台湾繁体字集,共包括国标繁体汉字13053个。
* GBK字集是简繁字集,包括了GB字集、BIG5字集和一些符号,共包括21003个字符。
* GB18030是国家制定的一个强制性大字集标准,全称为GB18030-2000,它的推出使汉字集有了一个“大一统”的标准。

ANSI和Unicode big endia:
我们在Windows系统中保存文本文件时通常可以选择编码为ANSI、Unicode、Unicode big endian和UTF-8,这里的ANSI和Unicode big endia是什么编码呢?
ANSI:
使用2个字节来代表一个字符的各种汉字延伸编码方式,称为ANSI编码。
在简体中文系统下,ANSI编码代表GB2312编码,在日文操作系统下,ANSI编码代表JIS编码。
Unicode big endia:
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。
Unicode规范中推荐的标记字节顺序的方法是BOM(即Byte Order Mark)。
在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。
UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。
因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。
Windows就是使用BOM来标记文本文件的编码方式的。

编程语言与编码

C、C++、Python2内部字符串都是使用当前系统默认编码
Python3、Java内部字符串用Unicode保存
Ruby有一个内部变量$KCODE用来表示可识别的多字节字符串的编码,变量值为"EUC" "SJIS" "UTF8" "NONE"之一。
$KCODE的值为"EUC"时,将假定字符串或正则表达式的编码为EUC-JP。
同样地,若为"SJIS"时则认定为Shift JIS。若为"UTF8"时则认定为UTF-8。
若为"NONE"时,将不会识别多字节字符串。
在向该变量赋值时,只有第1个字节起作用,且不区分大小写字母。
"e" "E" 代表 "EUC","s" "S" 代表 "SJIS","u" "U" 代表 "UTF8",而"n" "N" 则代表 "NONE"。
默认值为"NONE"。
即默认情况下Ruby把字符串当成单字节序列来处理。

为什么会乱码?

乱码是个老问题,从上面我们知道,字符在保存时的编码格式如果和要显示的编码格式不一样的话,就会出现乱码问题。
我们的Web系统,从底层数据库编码、Web应用程序编码到HTML页面编码,如果有一项不一致的话,就会出现乱码。
所以,解决乱码问题说难也难说简单也简单,关键是让交互系统之间编码一致。

有没有万金油?

在如此多种编码和字符集弄的我们眼花缭乱的情况下,我们只需选择一种兼容性最好的编码方式和字符集,让它成为我们程序子系统之间
交互的编码契约,那么从此恼人的乱码问题即将远离我们而去 -- 这种兼容性最好的编码就是UTF-8!
毕竟GBK/GB2312是国内的标准,当我们大量使用国外的开源软件时,UTF-8才是编码界最通用的语言。

下面还有一篇关于编码的文章,转自http://kb.cnblogs.com/a/1540382/

这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,

增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题:


问题一:

使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种

编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?


我很早前就发现Unicode、Unicode bigendian和UTF-8编码的txt文件的开头会多出几个字节,

分别是FF、FE(Unicode),FE、FF(Unicode bigendian),EF、BB、BF(UTF-8)。但这些

标记是基于什么标准呢?


问题二:

最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。

对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不

起来UTF-16和UCS2有什么关系。查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些

Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要

求读者知道什么是字节,什么是十六进制。


0、big endian和little endian

bigendian和littleendian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。

那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。

如果将49写在前面,就是little endian。


“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)

敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,一个皇帝送了命,另一个丢了王位。

我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。


1、字符编码、内码,顺带介绍汉字编码

字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用

7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。

GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高

字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。

GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符

号区。汉字区包括21003个字符。

从ASCII、GB2312到GBK,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,

后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字

节的最高位不为0。按照程序员的称呼,GB2312、GBK都属于双字节字符集 (DBCS)。


2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、

蒙文、维吾尔文等主要的少数民族文字。从汉字字汇上说,GB18030在GB13000.1的20902个汉字的

基础上增加了CJK扩展A的6582个汉字(Unicode码0x3400-0x4db5),一共收录了27484个汉字。


CJK就是中日韩的意思。Unicode为了节省码位,将中日韩三国语言中的文字统一编码。GB13000.1

就是ISO/IEC 10646-1的中文版,相当于Unicode 1.1。


GB18030的编码采用单字节、双字节和4字节方案。其中单字节、双字节和GBK是完全兼容的。4字节

编码的码位就是收录了CJK扩展A的6582个汉字。例如:UCS的0x3400在GB18030中的编码应该是

8139EF30,UCS的0x3401在GB18030中的编码应该是8139EF31。


微软提供了GB18030的升级包,但这个升级包只是提供了一套支持CJK扩展A的6582个汉字的新字体:

新宋体-18030,并不改变内码。Windows 的内码仍然是GBK。


这里还有一些细节:

GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。


对于任何字符编码,编码单元的顺序是由编码方案指定的,与endian无关。例如GBK的编码单元是字

节,用两个字节表示一个汉字。这两个字节的顺序是固定的,不受CPU字节序的影响。UTF-16的编码

单元是word(双字节),word之间的顺序是编码方案指定的,word内部的字节排列才会受到endian

的影响。后面还会介绍UTF-16。


GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和

GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,

只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。


2、Unicode、UCS和UTF

前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼

容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,

而GB码是BABA。

Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码

方案。Unicode的学名是"UniversalMultiple-Octet Coded Character Set",简称为UCS。

UCS可以看作是"Unicode CharacterSet"的缩写。


根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计

Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开

发了ISO10646项目,Unicode协会开发了Unicode项目。


在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,

并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1

相同的字库和字码。


目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode

4.1.0。ISO的最新标准是ISO 10646-3:2003。


UCS只是规定如何编码,并没有规定如何传输、保存这个编码。例如“汉”字的UCS编码是6C49,我可

以用4个ascii数字来传输、保存这个编码;也可以用utf-8编码:3个连续的字节E6 B1 89来表示它。

关键在于通信双方都要认可。UTF-8、UTF-7、UTF-16都是被广泛接受的方案。UTF-8的一个特别的

好处是它与ISO-8859-1完全兼容。UTF是“UCS Transformation Format”的缩写。


IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的

编码方法。我总是记不得IETF是InternetEngineering Task Force的缩写。但IETF负责维护

的RFC是Internet上一切规范的基础。


2.1、内码和code page

目前Windows的内核已经支持Unicode字符集,这样在内核上可以支持全世界所有的语言文字。但是

由于现有的大量程序和文档都采用了某种特定语言的编码,例如GBK,Windows不可能不支持现有的

编码,而全部改用Unicode。


Windows使用代码页(code page)来适应各个国家和地区。code page可以被理解为前面提到的内码。

GBK对应的code page是CP936。


微软也为GB18030定义了code page:CP54936。但是由于GB18030有一部分4字节编码,而Windows

的代码页只支持单字节和双字节编码,所以这个code page是无法真正使用的。


3、UCS-2、UCS-4、BMP

UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节

(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:

UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。


UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。

每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后

一个字节不同,其余都相同。


group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节

为0的码位被称作BMP。


将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得

到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。


4、UTF编码


UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:

UCS-2编码(16进制) UTF-8 字节流(二进制)

0000 - 007F 0xxxxxxx

0080 - 07FF 110xxxxx 10xxxxxx

0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx


例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:

1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流

依次代替模板中的x,得到:1110011010110001 10001001,即E6 B1 89。


读者可以用记事本测试一下我们的编码是否正确。需要注意,UltraEdit在打开utf-8编码的文本文

件时会自动转换为UTF-16,可能产生混淆。你可以在设置中关掉这个选项。更好的工具是Hex Workshop。


UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应

的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,

或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2

只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。


5、UTF的字节序和BOM

UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16

文本前,首先要弄清楚每个编码单元的字节序。例如“奎”的Unicode编码是594E,“乙”的Unicode

编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?


Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是

Byte Order Mark。BOM是一个有点小聪明的想法:

在UCS编码中有一个叫做"ZERO WIDTH NO-BREAKSPACE"的字符,它的编码是FEFF。而FFFE在UCS

中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符

"ZERO WIDTH NO-BREAK SPACE"。


这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流

是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。


UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"

的UTF-8编码是EFBB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以

EF BB BF开头的字节流,就知道这是UTF-8编码了。



Windows就是使用BOM来标记文本文件的编码方式的。


6、进一步的参考资料

本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode"

(http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。


我还找了两篇看上去不错的资料,不过因为我开始的疑问都找到了答案,所以就没有看:


"Understanding
Unicode A general introduction to the Unicode Standard"
(http://scrīpts.sil.org/cms/scrīpts/page.php?site_id=nrsi&item_id=IWS-Chapter04a)

"Character set encoding basics Understanding character set
encodings and legacy encodings"
(http://scrīpts.sil.org/cms/scrīpts/page.php?site_id=nrsi&item_id=IWS-Chapter03)

我写过UTF-8、UCS-2、GBK相互转换的软件包,包括使用Windows API和不使用Windows API的

版本。以后有时间的话,我会整理一下放到我的个人主页上(http://fmddlmyy.home4u.china.com)。


我是想清楚所有问题后才开始写这篇文章的,原以为一会儿就能写好。没想到考虑措辞和查证细节花

费了很长时间,竟然从下午1:30写到9:00。希望有读者能从中受益。


附录1 再说说区位码、GB2312、内码和代码页

有的朋友对文章中这句话还有疑问:

“GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。”


我再详细解释一下:


“GB2312的原文”是指国家1980年的一个标准《中华人民共和国国家标准 信息交换用汉字编码字符集

基本集 GB2312-80》。这个标准用两个数来编码汉字和中文符号。第一个数称为“区”,第二个数称为

“位”。所以也称为区位码。1-9区是中文符号,16-55区是一级汉字,56-87区是二级汉字。现在Windows

也还有区位输入法,例如输入1601得到“啊”。(这个区位输入法可以自动识别16进制的GB2312和10

进制的区位码,也就是说输入B0A1同样会得到“啊”。)


内码是指操作系统内部的字符编码。早期操作系统的内码是与语言相关的。现在的Windows在系统内

部支持Unicode,然后用代码页适应各种语言,“内码”的概念就比较模糊了。微软一般将缺省代码页

指定的编码说成是内码。


内码这个词汇,并没有什么官方的定义,代码页也只是微软这个公司的叫法。作为程序员,我们只要

知道它们是什么东西,没有必要过多地考证这些名词。


所谓代码页(code page)就是针对一种语言文字的字符编码。例如GBK的code page是CP936,BIG5

的code page是CP950,GB2312的code page是CP20936。


Windows中有缺省代码页的概念,即缺省用什么编码来解释字符。例如Windows的记事本打开了一个

文本文件,里面的内容是字节流:BA、BA、D7、D6。Windows应该去怎么解释它呢?


是按照Unicode编码解释、还是按照GBK解释、还是按照BIG5解释,还是按照ISO8859-1去解释?如

果按GBK去解释,就会得到“汉字”两个字。按照其它编码解释,可能找不到对应的字符,也可能找到错

误的字符。所谓“错误”是指与文本作者的本意不符,这时就产生了乱码。


答案是Windows按照当前的缺省代码页去解释文本文件里的字节流。缺省代码页可以通过控制面板的

区域选项设置。记事本的另存为中有一项ANSI,其实就是按照缺省代码页的编码方法保存。


Windows的内码是Unicode,它在技术上可以同时支持多个代码页。只要文件能说明自己使用什么编

码,用户又安装了对应的代码页,Windows就能正确显示,例如在HTML文件中就可以指定charset。


有的HTML文件作者,特别是英文作者,认为世界上所有人都使用英文,在文件中不指定charset。

如果他使用了0x80-0xff之间的字符,中文Windows又按照缺省的GBK去解释,就会出现乱码。这时

只要在这个html文件中加上指定charset的语句,例如:

<meta http-equiv="Content-Type" content="text/html; charset=ISO8859-1">

如果原作者使用的代码页和ISO8859-1兼容,就不会出现乱码了。


再说区位码,啊的区位码是1601,写成16进制是0x10,0x01。这和计算机广泛使用的ASCII编码冲突。

为了兼容00-7f的ASCII编码,我们在区位码的高、低字节上分别加上A0。这样“啊”的编码就成为B0A1。

我们将加过两个A0的编码也称为GB2312编码,虽然GB2312的原文根本没提到这一点。


统下显示正好为上述编码的三个乱码汉字。

4.问题解决
可以通过FTP或其它一些同步工具将该文件上传到linux系统下。

5.知识拓展
字符(Character)是文字与符号的总称,包括文字、图形符号、数学符号等。一组抽象字符的集合就是字符集(Charset)。
计算机要处理各种字符,就需要将字符和二进制内码对应起来,这种对应关系就是字符编码(Encoding)。
制定编码首先要确定字符集,并将字符集内的字符排序,然后和二进制数字对应起来。根据字符集内字符的多少,会确定用几个字节来编码。
每种编码都限定了一个明确的字符集合,叫做被编码过的字符集(Coded Character Set),这是字符集的另外一个含义。通常所说的字符集大多是这个含义。

常用的字符集有:ASCII、ISO 8859-1、UCS、Unicode、UTF、汉字编码、ANSI
下面是转的各种编码方式的详细介绍:
ASCII:
American Standard Code for Information Interchange,美国信息交换标准码。
目前计算机中用得最广泛的字符集及其编码,由美国国家标准局(ANSI)制定。
它已被国际标准化组织(ISO)定为国际标准,称为ISO 646标准。
ASCII字符集由控制字符和图形字符组成。
在计算机的存储单元中,一个ASCII码值占一个字节(8个二进制位),其最高位(b7)用作奇偶校验位。
所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。
奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1。
偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。

ISO 8859-1:
ISO 8859,全称ISO/IEC 8859,是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准,现时定义了15个字符集。
ASCII收录了空格及94个“可印刷字符”,足以给英语使用。
但是,其他使用拉丁字母的语言(主要是欧洲国家的语言),都有一定数量的变音字母,故可以使用ASCII及控制字符以外的区域来储存及表示。
除了使用拉丁字母的语言外,使用西里尔字母的东欧语言、希腊语、泰语、现代阿拉伯语、希伯来语等,都可以使用这个形式来储存及表示。
* ISO 8859-1 (Latin-1) - 西欧语言
* ISO 8859-2 (Latin-2) - 中欧语言
* ISO 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。
* ISO 8859-4 (Latin-4) - 北欧语言
* ISO 8859-5 (Cyrillic) - 斯拉夫语言
* ISO 8859-6 (Arabic) - 阿拉伯语
* ISO 8859-7 (Greek) - 希腊语
* ISO 8859-8 (Hebrew) - 希伯来语(视觉顺序)
* ISO 8859-8-I - 希伯来语(逻辑顺序)
* ISO 8859-9 (Latin-5 或 Turkish) - 它把Latin-1的冰岛语字母换走,加入土耳其语字母。
* ISO 8859-10 (Latin-6 或 Nordic) - 北日耳曼语支,用来代替Latin-4。
* ISO 8859-11 (Thai) - 泰语,从泰国的 TIS620 标准字集演化而来。
* ISO 8859-13 (Latin-7 或 Baltic Rim) - 波罗的语族
* ISO 8859-14 (Latin-8 或 Celtic) - 凯尔特语族
* ISO 8859-15 (Latin-9) - 西欧语言,加入Latin-1欠缺的法语及芬兰语重音字母,以及欧元符号。
* ISO 8859-16 (Latin-10) - 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号。
很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。
但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。
而且在很多协议上,默认使用该编码。

UCS:
通用字符集(Universal Character Set,UCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的字符编码方式,采用4字节编码。
UCS包含了已知语言的所有字符。
除了拉丁语、希腊语、斯拉夫语、希伯来语、阿拉伯语、亚美尼亚语、格鲁吉亚语,还包括中文、日文、韩文这样的象形文字,UCS还包括大量的图形、印刷、数学、科学符号。
* UCS-2: 与unicode的2byte编码基本一样。
* UCS-4: 4byte编码, 目前是在UCS-2前加上2个全零的byte。

Unicode:
Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。
它是http://www.unicode.org制定的编码机制, 要将全世界常用文字都函括进去。
它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。
但自从unicode2.0开始,unicode采用了与ISO 10646-1相同的字库和字码,ISO也承诺ISO10646将不会给超出0x10FFFF的UCS-4编码赋值,使得两者保持一致。
Unicode的编码方式与ISO 10646的通用字符集(Universal Character Set,UCS)概念相对应,目前的用于实用的Unicode版本对应于UCS-2,使用16位的编码空间。
也就是每个字符占用2个字节,基本满足各种语言的使用。实际上目前版本的Unicode尚未填充满这16位编码,保留了大量空间作为特殊使用或将来扩展。

UTF:
Unicode 的实现方式不同于编码方式。
一个字符的Unicode编码是确定的,但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。
Unicode的实现方式称为Unicode转换格式(Unicode Translation Format,简称为 UTF)。
* UTF-8: 8bit变长编码,对于大多数常用字符集(ASCII中0~127字符)它只使用单字节,而对其它常用字符(特别是朝鲜和汉语会意文字),它使用3字节。
* UTF-16: 16bit编码,是变长码,大致相当于20位编码,值在0到0x10FFFF之间,基本上就是unicode编码的实现,与CPU字序有关。


汉字编码:
* GB2312字集是简体字集,全称为GB2312(80)字集,共包括国标简体汉字6763个。
* BIG5字集是台湾繁体字集,共包括国标繁体汉字13053个。
* GBK字集是简繁字集,包括了GB字集、BIG5字集和一些符号,共包括21003个字符。
* GB18030是国家制定的一个强制性大字集标准,全称为GB18030-2000,它的推出使汉字集有了一个“大一统”的标准。

ANSI和Unicode big endia:
我们在Windows系统中保存文本文件时通常可以选择编码为ANSI、Unicode、Unicode big endian和UTF-8,这里的ANSI和Unicode big endia是什么编码呢?
ANSI:
使用2个字节来代表一个字符的各种汉字延伸编码方式,称为ANSI编码。
在简体中文系统下,ANSI编码代表GB2312编码,在日文操作系统下,ANSI编码代表JIS编码。
Unicode big endia:
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。
Unicode规范中推荐的标记字节顺序的方法是BOM(即Byte Order Mark)。
在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。
UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。
因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。
Windows就是使用BOM来标记文本文件的编码方式的。

编程语言与编码

C、C++、Python2内部字符串都是使用当前系统默认编码
Python3、Java内部字符串用Unicode保存
Ruby有一个内部变量$KCODE用来表示可识别的多字节字符串的编码,变量值为"EUC" "SJIS" "UTF8" "NONE"之一。
$KCODE的值为"EUC"时,将假定字符串或正则表达式的编码为EUC-JP。
同样地,若为"SJIS"时则认定为Shift JIS。若为"UTF8"时则认定为UTF-8。
若为"NONE"时,将不会识别多字节字符串。
在向该变量赋值时,只有第1个字节起作用,且不区分大小写字母。
"e" "E" 代表 "EUC","s" "S" 代表 "SJIS","u" "U" 代表 "UTF8",而"n" "N" 则代表 "NONE"。
默认值为"NONE"。
即默认情况下Ruby把字符串当成单字节序列来处理。

为什么会乱码?

乱码是个老问题,从上面我们知道,字符在保存时的编码格式如果和要显示的编码格式不一样的话,就会出现乱码问题。
我们的Web系统,从底层数据库编码、Web应用程序编码到HTML页面编码,如果有一项不一致的话,就会出现乱码。
所以,解决乱码问题说难也难说简单也简单,关键是让交互系统之间编码一致。

有没有万金油?

在如此多种编码和字符集弄的我们眼花缭乱的情况下,我们只需选择一种兼容性最好的编码方式和字符集,让它成为我们程序子系统之间
交互的编码契约,那么从此恼人的乱码问题即将远离我们而去 -- 这种兼容性最好的编码就是UTF-8!
毕竟GBK/GB2312是国内的标准,当我们大量使用国外的开源软件时,UTF-8才是编码界最通用的语言。

下面还有一篇关于编码的文章,转自http://kb.cnblogs.com/a/1540382/

这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,

增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题:


问题一:

使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种

编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?


我很早前就发现Unicode、Unicode bigendian和UTF-8编码的txt文件的开头会多出几个字节,

分别是FF、FE(Unicode),FE、FF(Unicode bigendian),EF、BB、BF(UTF-8)。但这些

标记是基于什么标准呢?


问题二:

最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。

对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不

起来UTF-16和UCS2有什么关系。查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些

Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要

求读者知道什么是字节,什么是十六进制。


0、big endian和little endian

bigendian和littleendian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。

那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。

如果将49写在前面,就是little endian。


“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)

敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,一个皇帝送了命,另一个丢了王位。

我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。


1、字符编码、内码,顺带介绍汉字编码

字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用

7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。

GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高

字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。

GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符

号区。汉字区包括21003个字符。

从ASCII、GB2312到GBK,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,

后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字

节的最高位不为0。按照程序员的称呼,GB2312、GBK都属于双字节字符集 (DBCS)。


2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、

蒙文、维吾尔文等主要的少数民族文字。从汉字字汇上说,GB18030在GB13000.1的20902个汉字的

基础上增加了CJK扩展A的6582个汉字(Unicode码0x3400-0x4db5),一共收录了27484个汉字。


CJK就是中日韩的意思。Unicode为了节省码位,将中日韩三国语言中的文字统一编码。GB13000.1

就是ISO/IEC 10646-1的中文版,相当于Unicode 1.1。


GB18030的编码采用单字节、双字节和4字节方案。其中单字节、双字节和GBK是完全兼容的。4字节

编码的码位就是收录了CJK扩展A的6582个汉字。例如:UCS的0x3400在GB18030中的编码应该是

8139EF30,UCS的0x3401在GB18030中的编码应该是8139EF31。


微软提供了GB18030的升级包,但这个升级包只是提供了一套支持CJK扩展A的6582个汉字的新字体:

新宋体-18030,并不改变内码。Windows 的内码仍然是GBK。


这里还有一些细节:

GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。


对于任何字符编码,编码单元的顺序是由编码方案指定的,与endian无关。例如GBK的编码单元是字

节,用两个字节表示一个汉字。这两个字节的顺序是固定的,不受CPU字节序的影响。UTF-16的编码

单元是word(双字节),word之间的顺序是编码方案指定的,word内部的字节排列才会受到endian

的影响。后面还会介绍UTF-16。


GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和

GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,

只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。


2、Unicode、UCS和UTF

前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼

容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,

而GB码是BABA。

Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码

方案。Unicode的学名是"UniversalMultiple-Octet Coded Character Set",简称为UCS。

UCS可以看作是"Unicode CharacterSet"的缩写。


根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计

Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开

发了ISO10646项目,Unicode协会开发了Unicode项目。


在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,

并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1

相同的字库和字码。


目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode

4.1.0。ISO的最新标准是ISO 10646-3:2003。


UCS只是规定如何编码,并没有规定如何传输、保存这个编码。例如“汉”字的UCS编码是6C49,我可

以用4个ascii数字来传输、保存这个编码;也可以用utf-8编码:3个连续的字节E6 B1 89来表示它。

关键在于通信双方都要认可。UTF-8、UTF-7、UTF-16都是被广泛接受的方案。UTF-8的一个特别的

好处是它与ISO-8859-1完全兼容。UTF是“UCS Transformation Format”的缩写。


IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的

编码方法。我总是记不得IETF是InternetEngineering Task Force的缩写。但IETF负责维护

的RFC是Internet上一切规范的基础。


2.1、内码和code page

目前Windows的内核已经支持Unicode字符集,这样在内核上可以支持全世界所有的语言文字。但是

由于现有的大量程序和文档都采用了某种特定语言的编码,例如GBK,Windows不可能不支持现有的

编码,而全部改用Unicode。


Windows使用代码页(code page)来适应各个国家和地区。code page可以被理解为前面提到的内码。

GBK对应的code page是CP936。


微软也为GB18030定义了code page:CP54936。但是由于GB18030有一部分4字节编码,而Windows

的代码页只支持单字节和双字节编码,所以这个code page是无法真正使用的。


3、UCS-2、UCS-4、BMP

UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节

(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:

UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。


UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。

每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后

一个字节不同,其余都相同。


group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节

为0的码位被称作BMP。


将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得

到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。


4、UTF编码


UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:

UCS-2编码(16进制) UTF-8 字节流(二进制)

0000 - 007F 0xxxxxxx

0080 - 07FF 110xxxxx 10xxxxxx

0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx


例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:

1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流

依次代替模板中的x,得到:1110011010110001 10001001,即E6 B1 89。


读者可以用记事本测试一下我们的编码是否正确。需要注意,UltraEdit在打开utf-8编码的文本文

件时会自动转换为UTF-16,可能产生混淆。你可以在设置中关掉这个选项。更好的工具是Hex Workshop。


UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应

的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,

或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2

只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。


5、UTF的字节序和BOM

UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16

文本前,首先要弄清楚每个编码单元的字节序。例如“奎”的Unicode编码是594E,“乙”的Unicode

编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?


Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是

Byte Order Mark。BOM是一个有点小聪明的想法:

在UCS编码中有一个叫做"ZERO WIDTH NO-BREAKSPACE"的字符,它的编码是FEFF。而FFFE在UCS

中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符

"ZERO WIDTH NO-BREAK SPACE"。


这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流

是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。


UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"

的UTF-8编码是EFBB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以

EF BB BF开头的字节流,就知道这是UTF-8编码了。



Windows就是使用BOM来标记文本文件的编码方式的。


6、进一步的参考资料

本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode"

(http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。


我还找了两篇看上去不错的资料,不过因为我开始的疑问都找到了答案,所以就没有看:


"Understanding
Unicode A general introduction to the Unicode Standard"
(http://scrīpts.sil.org/cms/scrīpts/page.php?site_id=nrsi&item_id=IWS-Chapter04a)

"Character set encoding basics Understanding character set
encodings and legacy encodings"
(http://scrīpts.sil.org/cms/scrīpts/page.php?site_id=nrsi&item_id=IWS-Chapter03)

我写过UTF-8、UCS-2、GBK相互转换的软件包,包括使用Windows API和不使用Windows API的

版本。以后有时间的话,我会整理一下放到我的个人主页上(http://fmddlmyy.home4u.china.com)。


我是想清楚所有问题后才开始写这篇文章的,原以为一会儿就能写好。没想到考虑措辞和查证细节花

费了很长时间,竟然从下午1:30写到9:00。希望有读者能从中受益。


附录1 再说说区位码、GB2312、内码和代码页

有的朋友对文章中这句话还有疑问:

“GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。”


我再详细解释一下:


“GB2312的原文”是指国家1980年的一个标准《中华人民共和国国家标准 信息交换用汉字编码字符集

基本集 GB2312-80》。这个标准用两个数来编码汉字和中文符号。第一个数称为“区”,第二个数称为

“位”。所以也称为区位码。1-9区是中文符号,16-55区是一级汉字,56-87区是二级汉字。现在Windows

也还有区位输入法,例如输入1601得到“啊”。(这个区位输入法可以自动识别16进制的GB2312和10

进制的区位码,也就是说输入B0A1同样会得到“啊”。)


内码是指操作系统内部的字符编码。早期操作系统的内码是与语言相关的。现在的Windows在系统内

部支持Unicode,然后用代码页适应各种语言,“内码”的概念就比较模糊了。微软一般将缺省代码页

指定的编码说成是内码。


内码这个词汇,并没有什么官方的定义,代码页也只是微软这个公司的叫法。作为程序员,我们只要

知道它们是什么东西,没有必要过多地考证这些名词。


所谓代码页(code page)就是针对一种语言文字的字符编码。例如GBK的code page是CP936,BIG5

的code page是CP950,GB2312的code page是CP20936。


Windows中有缺省代码页的概念,即缺省用什么编码来解释字符。例如Windows的记事本打开了一个

文本文件,里面的内容是字节流:BA、BA、D7、D6。Windows应该去怎么解释它呢?


是按照Unicode编码解释、还是按照GBK解释、还是按照BIG5解释,还是按照ISO8859-1去解释?如

果按GBK去解释,就会得到“汉字”两个字。按照其它编码解释,可能找不到对应的字符,也可能找到错

误的字符。所谓“错误”是指与文本作者的本意不符,这时就产生了乱码。


答案是Windows按照当前的缺省代码页去解释文本文件里的字节流。缺省代码页可以通过控制面板的

区域选项设置。记事本的另存为中有一项ANSI,其实就是按照缺省代码页的编码方法保存。


Windows的内码是Unicode,它在技术上可以同时支持多个代码页。只要文件能说明自己使用什么编

码,用户又安装了对应的代码页,Windows就能正确显示,例如在HTML文件中就可以指定charset。


有的HTML文件作者,特别是英文作者,认为世界上所有人都使用英文,在文件中不指定charset。

如果他使用了0x80-0xff之间的字符,中文Windows又按照缺省的GBK去解释,就会出现乱码。这时

只要在这个html文件中加上指定charset的语句,例如:

<meta http-equiv="Content-Type" content="text/html; charset=ISO8859-1">

如果原作者使用的代码页和ISO8859-1兼容,就不会出现乱码了。


再说区位码,啊的区位码是1601,写成16进制是0x10,0x01。这和计算机广泛使用的ASCII编码冲突。

为了兼容00-7f的ASCII编码,我们在区位码的高、低字节上分别加上A0。这样“啊”的编码就成为B0A1。

我们将加过两个A0的编码也称为GB2312编码,虽然GB2312的原文根本没提到这一点。


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics