关于字符编码 GB2312,UTF-8,GBK,BIG5

关于字符编码 GB2312,UTF-8,GBK,BIG5
你是否对字符编码的问题还是不了解,是否遇到过文件乱码的问题呢,看到 ANSI,GB2312,GBK,latin-1,cp936,euc-cn,GB18030,big5。这么多的会不会头晕呢?
先看一个很早看到的一个奇怪现象:在 Windows 的记事本里新建一个文本文件,输入 “联通” 两个字,保存,关闭,再次打开,会发现文本已经不是 “联通” 了,而是几个乱码。这原因下面就会揭开。
各种字符编码
1. ASCII

ASCII(ISO 646) 编码大家都应该熟悉,是用一个 8 位的字节来表示空格、标点符号、数字、大小写字母或者控制字符的,其中最高位为 “0”,其他位可以自由组合成 128 个字符了,这就是 ASCII 的编码。
2. latin-1

latin-1 又称 ISO/IEC 8859-1,是国际标准化组织 ISO 字符编码标准 ISO/IEC 8859 的一部分,它在 ASCII 编码空置的 0xA0-0xFF 的范围内加入了其他符号字母以供西欧来使用,所以也叫 “西欧语言”,另外 ISO 的编码还有其他的一些语言编码,这些都是单字节 8 位编码。
3. ANSI

ANSI 是美国的国家标准协会,ANSI 的编码也是在 ASCII 的标准上扩展而来的,但 ANSI 的编码是双字节 16 位的编码,在简体中文的操作系统中 ANSI 就指的是 GB2312,而在日文的操作系统就指的是 JIS,这些编码之间互相不兼容,但所有的 ANSI 编码都兼容 ASCII 编码。
4. GB2312(1980)

GB2312 是对 ANSI 的简体中文扩展。GB2312 的原型是一种区位码,这种编码把常见的汉字分区,每个汉字有对应的区号和位号,例如:”我” 的区号是 46,位号是 50,这种区位码在上初中时还用一个小册子查过。GB2312 因要与 ASCII 相兼容,所以每个字的区号和位号都加上 0xA0 得到两个最高位都是 “1” 的 8 位字节(0xCED2, 11001110 11010010),这两个字节组合而成就是一个汉字的 GB2312 编码,GB2312 编码中小于 127 的字符与 ASCII 的相同。与区位码常提到的另一个词是 “内码”,字面意思就是计算机内部使用的二进制编码,也就是区位码加上 0xA0 得到的。因为 GB2312 无法对繁体中文编码,所以与之对应的繁体中文编码方式为 BIG5。
5. GBK(1995) 和 GB18030(2000)

GB2312 共收录了七千个字符,由于 GB2312 支持的汉字太少而且不支持繁体中文,所以 GBK 对 GB2312 进行了扩展,对低字节不再做大于 127 的规定,以支持繁体中文和更多的字符,GBK 共支持大概 22000 个字符,GB18030 是在 GBK 的基础上又增加了藏文、蒙文、维吾尔文等主要的少数民族文字。
6. UCS 和 unicode

由于各国之间的编码不同造成的交流传输不便,ISO 打算废除所有的地区性编码方案,重新建立一个全球性的编码方案把所有字母和符号都统一编码进去,称之为 “Universal Multiple-Octet Coded Character Set”,简称为 UCS(ISO10646),UCS分为 UCS-2 和 UCS-4 两个方案,UCS-2 采用 2 个字节来存储一个字符,共可以编码 216个字符(即 65536),这大概可以覆盖完世界上所有的字符,如果不够还可以采用 UCS-4 来编码,UCS-4 采用 31 位来编码,最高位为 “0”,大概有 21 亿个字符。UCS-4 高两个字节为 0 的码位被称作BMP(Basic Multilingual Plane, 基本多语言面),即将 UCS-4 的 BMP 去掉前面的两个零字节就得到了 UCS-2。在 UCS-2 的两个字节前加上两个零字节,就得到了 UCS-4 的 BMP。
在同时代又有 unicode.org 这个组织也制定了自己的全球性编码 unicode,unicode 1.0 的编码统一采用双字节编码,也可以编码 65536 个字符,unicode2.0 采用 20 位编码,编码范围为 0 到 0x10FFFF,由于这两种编码采用了不同的编码,也阻碍了交流,但自从 unicode2.0 开始,unicode 采用了与 USC 相同的字库和字码,ISO 也承诺将不会给超出 0x10FFFF 的 UCS-4 编码赋值,使得两者保持一致。现阶段主要采用的是 UCS-2/unicode 16 位的编码,这种定长编码便于计算机的处理,ASCII 在这种编码下就统一变成了高字节全是 “0”,低字节来编码,这种编码在英文存储中会浪费一倍的空间,但这些浪费在在现在存储器极度便宜的时代也算不得什么。
UCS 不仅给每个字符分配一个代码,而且赋予了一个正式的名字. 表示一个 Unicode/UCS 值的十六进制数,通常在前面加上 “U+”,就象 U+0041 代表字符 “拉丁大写字母A”。
7. UTF

UTF(Unicode/UCS Transfer Format),UCS 变长存储的编码方式,主要用来解决 UCS 编码的传输问题的。分为 UTF-7,UTF-8,UTF-16,UTF-32 等。
8. UTF-8

UTF-8 是一次传输 8 位 (一个字节) 的 UTF 编码方式,一个字符可能会经过 1-6 次传输,具体的跟 unicode/UCS 之间的转换关系如下:
unicode UTF-8
U+00000000 - U+0000007F: 0xxxxxxx
U+00000080 - U+000007FF: 110xxxxx 10xxxxxx
U+00000800 - U+0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U+00010000 - U+001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U+00200000 - U+03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U+04000000 - U+7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
比如: “我” 的 unicode/UCS 编码为 “U+6211”(01100010 00010001),在 U+00000800 - U+0000FFFF 之间,所以采用三字节编码,按规则分段为:0110 001000 010001,再分别替换上表中的x,得到11100110 10001000 10010001,即为 “E6 88 91”,这就是 “我” 的 UTF-8 编码。
UTF-8 的传输与字节顺序无关,可以在不同平台之间交流,并且容错能力高,任何一个字节损坏后,最多只会导致一个编码码位损失,不会链锁错误 (如 GB 码少一个字节就会整行乱码),所以建议在保存文件时尽量采用 UTF-8 的编码来保存文件。
再来看开头提到的那个奇怪现象,就不难解释了,当使用记事本新建文件时,默认的编码是 ANSI,输入中文就是 GB 系列的编码,”联通” 两字的编码为:
c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000
注意到了吗?第一二个字节、第三四个字节的起始部分的都是 “110” 和 “10”,正好与 UTF-8 规则里的两字节模板是一致的,于是再次打开记事本时,记事本就误认为这是一个 UTF-8 编码的文件,让我们把第一个字节的 110 和第二个字节的 10 去掉,我们就得到了 “00001 101010”,再把各位对齐,补上前导的 0,就得到了 “0000 0000 0110 1010”,这是 UNICODE 的 006A,也就是小写的字母 “j”,而之后的两字节用 UTF-8 解码之后是 0368,这个字符什么也不是。这就是只有 “联通” 两个字的文件没有办法在记事本里正常显示的原因。
而如果你在 “联通” 之后多输入几个其他字,其他的字的编码不见得又恰好是 110 和 10 开始的字节,这样再次打开时,记事本就不会坚持这是一个 UTF-8 编码的文件,而会用 ANSI 的方式解读之,这时乱码又不出现了。
9. UTF-16

UTF-16 是一次传输两个字节的 UTF 编码方式,现如今 Unicode/UCS 也主要采用 16 位编码,所以 UTF-16 的存储方式和 Unicode/UCS 的编码方式也相同。确切的说是和 UCS-2/unicode 16 的编码方式相同。
其他的一些问题
1. big endian 和 little endian

在 UTF-16 或者 UCS 的编码中经常遇到这两个选项,big endian 和little endian 是 CPU 处理多字节数的不同方式。例如“汉”字的 Unicode/UCS 编码是 6C49。那么写到文件里时,究竟是将 6C 写在前面,还是将 49 写在前面?如果将 6C 写在前面,就是 big endian。还是将 49 写在前面,就是 little endian。
这两个词语出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头 (Big-Endian) 敲开还是从小头 (Little-Endian) 敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。
我们一般将 endian 翻译成 “字节序”,将 big endian 和 little endian 称作 “大尾” 和 “小尾” 。
2. UTF 的字节序和 BOM

BOM 称为 “Byte Order Mark”。UTF-8 以字节为编码单元,没有字节序的问题。而 UTF-16 以两个字节为编码单元,在解释一个 UTF-16 文本前,首先要弄清楚每个编码单元的字节序。例如收到一个 “奎” 的 Unicode/UCS 编码是 594E,”乙” 的 Unicode/UCS 编码是 4E59。如果我们收到 UTF-16 字节流 “594E”,那么这是 “奎” 还是 “乙”?
在 Unicode/UCS 编码中有一个叫做 “ZERO WIDTH NO-BREAK SPACE” 的字符,它的编码是 FEFF。而 FFFE 在 Unicode/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 编码是 EF BB BF。所以如果接收者收到以 EF BB BF 开头的字节流,就知道这是 UTF-8 编码了。Windows 就是使用 BOM 来标记文本文件的编码方式的。
3. UCS 的组合字符和实现级别

UCS 的组合字符指的是一些单个字符不是一个完整的字符,它是一个类似于重音符或其他指示标记,加在前一个字符后面。比如说重音符号,汉语拼音的音调,还比如 “菊花文” (我҉是҉菊҉花҉文҉) 中的菊花 (U+0489),组合字符机制允许在任何字符后加上重音符或其他指示标记, 这在科学符号中特别有用,比如数学方程式和国际音标字母,可能会需要在一个基本字符后组合上一个或多个指示标记。
不是所有的系统都需要支持象组合字符这样的 UCS 里所有的先进机制. 因此 ISO 10646 指定了下列三种实现级别:
级别 1
不支持组合字符和 Hangul Jamo 字符 (一种特别的,更加复杂的韩国文的编码,使用两个或三个子字符来编码一个韩文音节);
级别 2
类似于级别 1,但在某些文字中,允许一列固定的组合字符 (例如,希伯来文,阿拉伯文,Devangari,孟加拉语,果鲁穆奇语,Gujarati,Oriya,泰米尔语,Telugo,印.埃纳德语,Malayalam,泰国语和老挝语)。如果没有这最起码的几个组合字符,UCS 就不能完整地表达这些语言。
级别3
支持所有的 UCS 字符,例如数学家可以在任意一个字符上加上一个 tilde (颚化符号,西班牙语字母上面的~) 或一个箭头 (或两者都加)。
4. Unicode 和 UCS 的区别

Unicode 标准额外定义了许多与字符有关的语义符号学,一般而言是对于实现高质量的印刷出版系统的更好的参考。Unicode 详细说明了绘制某些语言 (比如阿拉伯语) 表达形式的算法,处理双向文字 (比如拉丁与希伯来文混合文字) 的算法和排序与字符串比较所需的算法,以及其他许多东西。
另一方面,UCS(ISO-10646) 标准,只不过是一个简单的字符集表。它指定了一些与标准有关的术语。定义了一些编码的别名。并包括了规范说明。指定了怎样使用 UCS 连接其他 ISO 标准的实现,比如 ISO-6429 和 ISO-2022。还有一些与 ISO 紧密相关的,比如 ISO-14651 是关于 UCS 字符串排序的。
考虑到 Unicode 标准有一个易记的名字,且在任何好的书店里的 Addison-Wesley 里有,只花费 ISO 版本的一小部分,且包括更多的辅助信息,因而它成为使用广泛得多的参考也就不足为奇了。然而,一般认为,用于打印 ISO-10646-1 标准的字体在某些方面的质量要高于用于打印 Unicode 2.0 的。专业字体设计者总是被建议说要两个标准都实现,但一些提供的样例字形有显著的区别。ISO-10646-1 标准同样使用四种不同的风格变体来显示表意文字如中文,日文和韩文 (CJK),而 Unicode 2.0 的表里只有中文的变体。这导致了普遍的认为 Unicode 对日本用户来说是不可接收的传说,尽管是错误的。
5. 代码页 (codepage)

所谓代码页 (codepage) 就是各国的文字编码和 Unicode 之间的映射表。例如 GBK 和 Unicode 的映射表就是 CP936,所以也常用 cp936 来指代 GBK。
6. 实例

写了这么多了,举一个例子吧
打开 “记事本” 程序 Notepad.exe,新建一个文本文件,内容就是一个 “我” 字,依次采用 ANSI,Unicode,Unicode big endian 和 UTF-8 编码方式保存。
然后,用文本编辑软件 UltraEdit 中的 “十六进制功能”,观察该文件的内部编码方式。
1)ANSI:文件的编码就是两个字节 “CE D2”,这正是 “我” 的 GB2312 编码,这也暗示 GB2312 是采用大尾方式存储的。
2)Unicode:编码是四个字节 “FF FE 11 62”,其中 “FF FE” 表明是小头方式存储,真正的编码是 U+6211。
3)Unicode big endian:编码是四个字节 “FE FF 62 11”,其中 “FE FF” 表明是大头方式存储。
4)UTF-8:编码是六个字节 “EF BB BF E6 88 91”,前三个字节 “EF BB BF” 表示这是 UTF-8 编码,后三个 “E6 88 91” 就是 “我” 的具体编码,它的存储顺序与编码顺序是一致的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用 Python 的内置函数 `open()` 打开二进制文件。然后,可以使用内置的 `read()` 函数读取文件的内容。 为了能够正确地解码文件中的中文汉字,需要指定文件的编码格式。如果不确定文件的编码格式,可以使用 Python 的 `chardet` 库来检测编码格式。 下面是一个示例代码,假设文件名为 `file.bin`: ```python import chardet # 检测文件的编码格式 with open('file.bin', 'rb') as f: data = f.read() result = chardet.detect(data) encoding = result['encoding'] # 使用检测到的编码格式打开文件 with open('file.bin', 'r', encoding=encoding) as f: content = f.read() print(content) ``` 上面的代码首先使用 `chardet` 库检测文件的编码格式,然后使用检测到的编码格式来打开文件。最后,使用 `read()` 函数读取文件内容并打印出来。 ### 回答2: 使用Python打开二进制文件并打印不同编码格式的中文汉字可以通过以下步骤实现: 1. 首先,使用Python的内置open()函数以二进制模式打开文件,指定文件路径和打开模式。 2. 读取文件内容,使用read()函数将文件内容读取为字节流,存储在一个变量中。 3. 通过不同编码格式对字节流进行解码,并打印出中文汉字。 下面是一个示例代码: ```python # 打开二进制文件 with open('file.bin', 'rb') as file: # 读取文件内容 data = file.read() # 打印GB2312编码中文汉字 print(data.decode('gb2312')) # 打印GB18030编码中文汉字 print(data.decode('gb18030')) # 打印GBK编码中文汉字 print(data.decode('gbk')) # 打印BIG5编码中文汉字 print(data.decode('big5')) # 打印Unicode编码中文汉字 print(data.decode('unicode_escape')) # 打印UTF-8编码中文汉字 print(data.decode('utf-8')) # 打印UTF-16 BE(大端序)编码中文汉字 print(data.decode('utf-16-be')) # 打印UTF-16 LE(小端序)编码中文汉字 print(data.decode('utf-16-le')) ``` 请确保将代码中的`file.bin`替换为实际的二进制文件路径。这样,代码会使用给定的编码格式打印出文件中相应格式的中文汉字。 ### 回答3: 首先,要使用Python打开一个二进制文件,可以通过以下步骤实现: 1. 使用`open()`函数以二进制模式打开文件,指定参数`'rb'`。 2. 使用`read()`函数读取文件的内容,并将其保存在一个变量中。 接下来,我们需要将这些二进制数据解码为不同的编码格式来打印中文汉字。下面是针对不同编码格式的解码方法: 1. 对于GB2312编码,可以使用`decode()`函数将二进制数据解码为字符串,并指定参数`'gb2312'`。 2. 对于GB18030编码,可以使用`decode()`函数将二进制数据解码为字符串,并指定参数`'gb18030'`。 3. 对于GBK编码,可以使用`decode()`函数将二进制数据解码为字符串,并指定参数`'gbk'`。 4. 对于BIG5编码,可以使用`decode()`函数将二进制数据解码为字符串,并指定参数`'big5'`。 5. 对于unicode编码,可以使用`decode()`函数将二进制数据解码为字符串,并指定参数`'unicode'`。 6. 对于UTF-8编码,可以使用`decode()`函数将二进制数据解码为字符串,并指定参数`'utf-8'`。 7. 对于UTF-16 BE编码,可以使用`decode()`函数将二进制数据解码为字符串,并指定参数`'utf-16 be'`。 8. 对于UTF-16 LE编码,可以使用`decode()`函数将二进制数据解码为字符串,并指定参数`'utf-16le'`。 最后,我们可以使用`print()`函数将解码后的字符串打印出来。 以下是一个示例代码,实现了打开一个二进制文件,并以不同的编码格式打印出中文汉字: ```python # 打开二进制文件 with open('binary_file.bin', 'rb') as file: content = file.read() # 解码并打印中文汉字 print(content.decode('gb2312')) print(content.decode('gb18030')) print(content.decode('gbk')) print(content.decode('big5')) print(content.decode('unicode')) print(content.decode('utf-8')) print(content.decode('utf-16 be')) print(content.decode('utf-16le')) ``` 请注意,以上代码中的`binary_file.bin`是一个示例二进制文件名,你需要将其替换为你要打开的实际二进制文件的路径或文件名。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值