编码的引入ASCII有人用 0x41 代表a,有人用 0x81 表示。语言不通,不同的计算机无法交流。美国人很早发现了这种问题,为便于交流指定了编码标准,于是有了: ASCII(American Standard Code for Information) ASCII码是7位编码,但由于计算机基本处理单位为字节(1byte = 8bit),所以一般仍以一个字节来存放一个ASCII字符。每一个字节中多余出来的一位(最高位)在计算机内部通常保持为0。 ASCII被定为国际标准之后的代号为ISO-646 ASCII 解决了美国人的问题,但很快,其他国家发现了这个编码不能满足自己国家的需要。法国、德国等国家暂且不说,英国都发现ASCII有问题:英镑符号“£”去哪儿了?现在好了,既想与ASCII兼容,有要添加ASCII没有的文字符号,怎么办?扩展一下吧! 由于ASCII码只使用了7个二进制位,也就是说一个字节可以表示的256个数字中,它仅使用了0~127这128个码位,剩下的128个码位便可以用来做扩展,用来表示一些特定语言所独有的字符 因此对这多余的128个码位的不同扩展,就形成了一系列ISO-8859-*的标准。例如为英语作了专门扩展的字符集编码标准编号为ISO-8859-1,也叫做Latin-1。 ISO-8859-*(*代表1~11,13~16)共15个编码方案,解决了拉丁字母的语言(主要是欧洲国家的语言),使用西里尔字母的东欧语言、希腊语、泰语、现代阿拉伯语、希伯来语等。 *这些编码方案在当时解决了这些国家的问题,但不同的编码如同军阀割据,同一势力范围内交流没问题,但不能普遍通用。比如 \xA3 在使用 Latin-1 的国家看来,是英镑符号“£”,而在使用 Latin-2的国家看来,却是另一个符号 “?”。而且无法在一个文件内同时出现这两个符号。这个问题的解决,需要unicode。
ISO-2022 与 EUCISO-8859-*解决了多数语言的编码问题,可是,汉语、日语及韩语字数众多,无法用单一个8位字符来表达,也就是无法通过类似 ISO-8859-*的方式解决。于是有了 ISO-2022。 ISO-2022提供了这样一种技术,它能在一种字符编码中支持多种字符集,可以用8位或16位来表示一个文字(字符),是一种变长的编码,这样,就能表示中日韩的字符了。该编码还有个显著的特点,就是所有的字节都是以0开始的。 ISO-2022在日本用的比较普遍,在中国反倒是很少使用。尽管如此,还是用中文的ISO-2022-CN 简单说一下: 在继续之前,我们先提下GB2312:GB2312 中规定了汉字的区位码(对应的二进制表,可以看做后面说的编码方案)。那么,如果指定 ISO-2022 (等同GB 2311) 作为为其包装方式(用来避免和ASCII的冲突)。 这里不得不介绍下ASCII控制字符了,既ASCII的0-31位表示其实都不是字符的编码而是用作它途 附上一张表:
汉字“文”字在46区36位,然后用 ISO 2022 包装时,字节序列是: <ESC>$ ) A <SO> <0x4E> <0x44> <SI> ISO2022使用“逃逸字串”(Escape sequence)。逃逸字串由1个“ESC”字符(0x1B),再由两至三个字串组成。此标记代表它后面的字符,属于下表字符集的文字。 <ESC> 是字节 0x1b,表示换码 然后ESC $ ) A 转为GB2312-1980 (2 bytes per character) <SO> 是字节 0x0e,表示脱离普通 ASCII 编码模式,进入特殊编码模式(这儿进入的是 GB 2312-1980 编码方式) <SI> 是字节 0x0f,表示返回普通 ASCII 编码模式 第一字节 0x4E 是在区号 46 的基础上加上32,以避开 ASCII 的控制符区(<32) 第二字节 0x44 是在位号 36 的基础上加上32,以避开 ASCII 的控制符区(<32) ISO/IEC2022 - Wikipedia, the free encyclopedia
前面说了,ISO-2022-CN 在国内很少使用,那么国内用的什么编码方案呢? 那就是 EUC-CN: EUC(Extended Unix Code),是一个主要用于日文、韩文、简体中文的多字节编码系统,它基于ISO-2020标准。 它使用了一些兼容于ISO-2022区位码的94x94编码表,把每个区位加上0xA0来表示,以便兼容于ASCII。 今天通常说的 GB2312 编码都是指 EUC(ISO-2020) 包装的GB2312 编码。 Extended Unix Code - Wikipedia, thefree encyclopedia EUC-GB2312 与区位码的关系: 第1字节 = 区码 + 32 + 0x80 第2字节 = 位码 + 32 + 0x80 所以,“文”字的 EUC-GB2312 编码是 0xCE 0xC4,用记事本保存一个“文”,然后用FlexHex查看 当然,大端小端问题和BOM一起说。
UnicodeUnicode是由于传统的字符编码方式的局限性而产生的,例如: ISO8859所定义的字符虽然在不同的国家中广泛地使用,可是在不同国家间却经常出现不相容的情况。 很多传统的编码方式都具有一个共通的问题,即其容许电脑进行双语环境式的处理(通常是ASCII以及其本地语言),但却无法同时支援多语言环境式的处理(比如同是处理中文和日文)。 于是一个将所有国家所有语种的所有文字进行统一编码的方案开始了... Unicode与UCS1980年代,有两个组织分别开始开发适用于各国语言的通用码,但不久他们便发现了对方的存在。 Unicode组织,由多家计算机软件公司,还包括一些出版行业的公司共同发起的。采用16位编码空间。 ISO-10646项目组,UniversalCharacter Set(UCS),采用31位编码空间。 ISO与Unicode是两个不同的组织,因此最初制定了不同的标准;但自从unicode2.0开始,unicode采用了与ISO 10646-1相同的字库和字码,ISO也承诺ISO10646将不会给超出0×10FFFF的UCS-4编码赋值,使得两者保持一致。 最终,两者统一了抽象字符集(即任何一个在Unicode中存在的字符,在UCS中也存在),且最靠前的65535个字符也统一了字符的编码。 现在可以认为UCS和UNICODE是一个概念。 字符编码方案在传统意义上,没有字符集和编码的区分,比如GB2312、Latin1等都是既指代字符集又指代编码方案。 编码字符集 CodedCharacter Set 字符编码 CharacterEncoding 按照惯例,人们认为字符集和字符编码是同义词,但现代的编码方案 Unicode,没有遵循这种惯例。 Unicode 是一个字符集合,它给集合中的每个字符都指定一了个代号code point。 字符到代号的过程。更简单的说就是指Unicode字符平面映射.(U+0000~ U+FFFF 这个序列中,每一个代号对应一个字符,不同的字符在字符平面上都有其对应的代号) 当我们要把这个代号存到计算机中时,需要把它变成一个字节的序列。 代号如果转换成到机器序列(机器编码)呢?这个过程有些可能为了节省空间而修改一些内容(utf8,utf16)... 于是不同的变换方式引入了: 小结一下: Unicode是规范,是编码字符集,规定了字符到字符平面代号的映射关系,其有UCS2和UCS4两种格式。UCS2和UCS4都是定长的,而不是字符编码方案。 UTF是Unicode Transformation Format,是Unicode的实现,是字符编码方案,规定了字符平面代号到机器编码(保存传输)的关系。 它分为utf-8,utf-16,utf-32几种形式,其中utf-8和utf-16都是变长的,而utf-32是定长编码。(其实还有utf-7等其它编码存在) 基本上可以理解为UTF-32就是UCS4,而UTF-16和UCS2兼容,只是UTF-16扩展了一些。 UTFUTF-8,8bit编码, ASCII不作变换, 其他字符做变长编码, 每个字符1-6 byte. 通常作为外码. 有以下优点: 1.与CPU字节顺序无关, 可以在不同平台之间交流 2.容错能力高, 任何一个字节损坏后, 最多只会导致一个编码码位损失, 不会链锁错误(如GB码错一个字节就会整行乱码) UTF-8用一个字节表示ASCI字符,用两个字节表示西欧字符,用三个字符表示亚洲的大部分字符。 UTF-16,16bit编码, 是变长码, UTF-16则是unicode的preferred encoding。 UCS-2与UTF-16在对前65536个字符的处理上完全相同,唯一的区别只在于 UCS-2 不支持surrogate pair机制,即是说,UCS-2只能对前65536个字符编码,对其后的字符毫无办法。 UTF-16是windows平台上主要使用的编码方案,一般windows上所说的UNICODE就是指它了. UTF-32,仅使用了unicode范围(0到0x10FFFF)的32位编码, 相当于UCS-4的子集.
BOMUTF还涉及到字节序的问题。字节顺序标记(Byte Order Mark, 简称BOM)出现在Unicode流开端,说明编码类型。BOM是一个有点小聪明的想法。 关于字节序问题。例如“奎”的Unicode编码是U+594E,“乙”的Unicode编码是U+4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”? Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill OfMaterial”的BOM表,而是Byte OrderMark。BOM是一个有点小聪明的想法: 在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是0xFEFF。(标准其实算是Big Endian的) Zero-widthnon-breaking space (ZWNBSP) is a deprecated use of the Unicode character atcode point U+FEFF. Character U+FEFF is intended for use as a Byte Order Markat the start of a file. However, if encounteredelsewhere it should, according to Unicode, be treated as a "zero-widthnon-breaking space". The deliberate use of U+FEFF for this purpose is nowdeprecated, with U+2060 word joiner (HTML: ⁠) strongly preferred. 而不同的编码方案解析这个"ZERO WIDTH NO-BREAKSPACE"字符 时就会产生不同的结果:
由于utf-8的big endian和littleendian结果都是一样的,linux下就默认不需要BOM,而Windows下默认没有BOM的文本是DBCS编码(GBK),所以Windows下如果要使用utf-8的话就需要带上BOM了,这是一个不兼容的地方,linux下如果带上utf-8的BOM就会报错。 注意:在UCS中是不存在的"U+FFFE" code point的,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。 这样传输Unicode字符时,如果接收者收到FEFF,就表明他是utf-16,并且它的字节序是Big-Endian的;如果是FFFE,就表明字节序是Little-Endian的。 因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。我们可以在保存和读取文本文件时用到它。 GB18030GB18030: Unicode 的GBK扩展版本, 覆盖了所有unicode编码, 地位等同于UTF-8, UTF-16, 是一种unicode编码形式. 变长编码, 用单字节/双字节/4字节对字符编码 GB18030向下兼容GB2312/GBK. GB18030 是中国所有非手持/嵌入式计算机系统的强制实施标准. base64Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一 Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,可以估算编码后数据长度大约为原长的135.1%。 ASCII中有很多字符是不可打印的,26个英文字母大小写加上0~9已经有62个字符了,再加上"+"和"/"正好凑了64和字符 base64可以用来将二进制的字节序列数据编码成ASCII字符序列构成的文本,也就是说图片之类的也可以用base64直接编码. HZ将 EUC-GB2312 的各字节最高位去掉,再在前后分别加上转义字符, 例如“文”: ~{<0x4E> <0x44> ~} 源文件内的编码与执行时字符编码我们先列个表,看看两种乱码分别在那种情况下出现: 我们只列举大家最常用的3个编译器(微软VS的中的cl,Mingw中的g++,Linux下的g++),源代码分别采用 GBK 和 不带BOM的UTF-8 以及 带BOM的UTF-8 这3中编码进行保存。
采用3种不同编码保存的源代码文件,分别用3种不同的编译器编译,形成9种组合,除掉一种不能工作的情况,两种乱码出现的情况各占一半。 从中我们也可以看出,乱码和操作系统原本是没有关系的。 但我们在 Windows 一般用的GBK,linux一般用的是不带BOM的UTF-8。 如果我们只考虑带*的情况,也可以说两种乱码和系统有关。
默认情况下
对 cl 只要源代码带BOM,如果源代码有BOM,对于不带L的字符串,直接转GBK。 如果不带BOM,编译器默认源代码文本是gbk编码,再进行相关的转换。 于是,当我们用不带BOM的utf8格式时,cl将其按照gbk解码。 对 gcc 默认将所有的字符串都按照utf8解码。带不带utf8的BOM都不影响。 其实gcc是支持其他编码的,只不过需要通过编译选项来指定。比如对gbk:gcc main.c-finput-charset=gbk -o main VS2010如果源码文件不带BOM的话,是不自动转换编码的,直接使用源码中的编码。使用BOM则会自动转换成GBK编码。 要禁止编码自动转换成GBK,可以加一个指令,更改执行字符集: #pragmaexecution_character_set("UTF-8") 上面提到的是"文件保存时需要BOM",也就是它需要知道"源码文件的编码",才能知道如何转换成"执行时字符集"。可以想到,一旦去掉BOM后,cl编译器只能靠猜测,这是不可靠的。
但是 目前GCC编译器不接受带有BOM的源代码文件,而且Linux/UNIX 并没有使用 BOM,因为它会破坏现有的 ASCII 文件的语法约定,所以问题应该还只是部分解决。
再来说一说CodePage在1980年前,仍然没有任何国际标准如ISO-8859或Unicode来定义如何扩展US-ASCII编码以便非英语国家的用户使用.于是很多IT 厂商发明了他们自己的编码,并且使用了难以记忆的数目来标识: ISO-8859-*,ISO-2022 与 EUC,… 代码页是字符集编码的别名,也称"内码表",是特定语言的字符集的一张表。早期,代码页是IBM称呼计算机的BIOS所支持的字符集编码。当时通用的操作系统都是命令行界面,这些操作系统直接使用BIOS提供的字符绘制功能来显示字符。这些BIOS代码页也被称为OEM代码页。图形操作系统使用自己的字符呈现引擎(rendering engine),可以支持多个不同的字符集编码,这类代码页被称作ASCII代码页。 早期IBM和微软内部使用数字来标记不同的编码字符集,不同的厂商对同一个字符集编码使用各自不同的名称。例如,UTF-8在IBM称作代码页1208, 在微软称作代码页65001, 在SAP称作代码页4110.
1987年4月,IBM发布了PC-DOS 3.3,正式开始使用16比特的无符号整数标识不同的代码页。这时的PC机使用CGA显示系统的字符界面,绘制不同语言的字符依靠BIOS硬件厂商提供的功能。如果想更换所支持的字符集,就必须换上支持该字符集的ROM芯片。微软作为DOS操作系统的软件厂商,并不拥有绘制这些字符集的知识产权。所以这些字符集的绘制实现,称作OEM代码页。最常见、最具代表性的OEM代码页是"IBM PC或MS-DOS 代码页437"。 随着图形用户界面操作系统的广泛使用(最初被广为接受的是Windows 3.1),操作系统具有了字符绘制的功能。微软在Windows操作系统没有转向UTF-16作为内码实现之前(也就是在Windows 2000之前),针对不同的使用地区与国家,定义了一系列的支持不同语言字符集的代码页,被称作"Windows (或ANSI) 代码页"。代表性的是实现了ISO-8859-1的代码页1252. 要使用CodePage的原因还有,很多符号形状(字体)的映射都是基于本地方案,既不是unicode字体。(个人猜测) 字符内码(charcter code)指的是用来代表字符的内码.读者在输入和存储文档时都要使用内码,内码分为 单字节内码 -- Single-Byte character sets (SBCS),可以支持256个字符编码. 双字节内码 -- Double-Byte character sets)(DBCS),可以支持65000个字符编码.主要用来对大字符集的东方文字进行编码. codepage指的是一个经过挑选的以特定顺序排列的字符内码列表,对于早期的单字节内码的语种,codepage中的内码顺序使得系统可以按照此列表来根据键盘的输入值给出一个对应的内码.对于双字节内码,则给出的是MultiByte到Unicode的对应表,这样就可以把以Unicode形式存放的字符转化为相应的字符内码,或者反之,在Linux核心中对应的函数就是utf8_mbtowc和utf8_wctomb. 现在来看这些Codpage 是不是很容易理解了? 中日韩 Codepage同 Extended Unix Coding ( EUC )编码大不一样的是,下面所有的远东 codepage 都利用了C1控制码{ 80.9F } 做为首字节, 使用ASCII值 { =407E { 做为第二字节,这样才能包含多达数万个双字节字符,这表明在这种编码之中小于3F的ASCII值不一定代表ASCII字符. CP932Shift-JIS包含日本语 charset JIS X 0201 (每个字符一个字节) 和 JIS X 0208 (每个字符两个字节),所以 JIS X 0201平假名包含一个字节半宽的字符,其剩馀的60个字节被用做7076个汉字以及648个其他全宽字符的首字节.同EUC-JP编码区别的是,Shift-JIS没有包含JIS X 202中定义的5802个汉字. CP936GBK扩展了 EUC-CN 编码( GB 2312-80编码,包含 6763 个汉字)到Unicode (GB13000.1-93)中定义的20902个汉字,中国大陆使用的是简体中文zh_CN. CP949UnifiedHangul(UHC) 是韩文 EUC-KR 编码(KS C 5601-1992 编码,包括2350 韩文音节和 4888 个汉字a)的超集,包含 8822个附加的韩文音节( 在C1中 ) CP950是代替EUC-TW (CNS 11643-1992)的 Big5 编码(13072 繁体 zh_TW 中文字)繁体中文,这些定义都在Ken Lunde的 CJK.INF中或者 Unicode 编码表中找到. 特别的:和Unicode相关的CodePage CP1200 — UCS-2LE Unicode 小端序 CP1201 — UCS-2BE Unicode 大端序 CP65000 — UTF-7 Unicode CP65001 — UTF-8 Unicode Linux下Codepage在Linux下引入对Codepage的支持主要是为了访问FAT/VFAT/FAT32/NTFS/NCPFS等文件系统下的多语种文件名的问题,目前在NTFS和FAT32/VFAT下的文件系统上都使用了Unicode,这就需要系统在读取这些文件名时动态将其转换为相应的语言编码.因此引入了NLS支持.其相应的程序文件在/usr/src/linux/fs/nls下: Config.in,Makefile ,nls_base.c ,nls_cp437.c ,nls_cp737.c ,nls_cp775.c ,nls_cp850.c ,nls_cp852.c ,nls_cp855.c ,nls_cp857.c ,nls_cp860.c ,nls_cp861.c ,nls_cp862.c ,nls_cp863.c ,nls_cp864.c ,nls_cp865.c ,nls_cp866.c ,nls_cp869.c ,nls_cp874.c ,nls_cp936.c ,nls_cp950.c ,nls_iso8859-1.c ,nls_iso8859-15.c ,nls_iso8859-2.c ,ls_iso8859-3.c ,nls_iso8859-4.c ,nls_iso8859-5.c ,,nls_iso8859-6.c ,nls_iso8859-7.c ,nls_iso8859-8.c ,nls_iso8859-9.c ,nls_koi8-r.c 实现了下列函数:
这样在加载相应的文件系统时就可以用下面的参数来设置Codepage: 对于Codepage 437 来说 mount-t vfat /dev/hda1 /mnt/1 -o codepage=437,iocharset=cp437 这样在Linux下就可以正常访问不同语种的长文件名了.
利用iconv函数族进行编码转换
用于char_set的参数列表
Windows下的编码转换
codepage在MSDN定义如下
利用ICU进行编码转换
编码的识别来自ByVoid 郭家宝 为什么用universalchardet?其实编码自动识别的解决方案不止一个,有icu提供的解决方案,IE也有API,还有已经在很多Linux发行版中的enca。之所以用universalchardet,是因为它是最合适的。IE的API不能跨平台,icu实现太庞大,enca是GPL(注意不是LGPL),使用它意味着我也要让我的所有源码使用GPL,而不是更加开放的Apache。universalchardet是MPL的,和LGPL差不多宽松,使用它是没有问题的。我非常不喜欢以GPL发布的函数库,这给开发者的限制太大了。 转自:http://blog.csdn.net/wangjieest/article/details/8097035 |
|