字符编码
什么是字符编码
字符编码(英语:Character encoding)也称字集码,是把 中的 为指定 中某一 (例如: 模式、 、8位组或者 ),以便 在 中存储和通过 的传递。常见的例子包括将 编码成 和 。其中, 将字母、数字和其它符号 ,并用7 的 来表示这个整数。通常会额外使用一个扩充的比特,以便于以1个 的方式存储。
在计算机技术发展的早期,如 (1963年)和 (1964年)这样的字符集逐渐成为标准。但这些字符集的局限很快就变得明显,于是人们开发了许多方法来扩展它们。对于支持包括东亚 字符家族在内的 的要求能支持更大量的字符,并且需要一种系统而不是临时的方法实现这些字符的编码。
简单地说:字符编码就是将人识别的字符转换计算机能识别的01,转换的规则就是字符编码表
编码
在显示器上看见的文字、图片等信息在电脑里面其实并不是我们看见的样子,即使你知道所有信息都存储在硬盘里,把它拆开也看不见里面有任何东西,只有些盘片。假设,你用显微镜把盘片放大,会看见盘片表面凹凸不平,凸起的地方被磁化,凹的地方是没有被磁化;凸起的地方代表数字1,凹的地方代表数字0。硬盘只能用0和1来表示所有文字、图片等信息。那么字母”A”在硬盘上是如何存储的呢?可能小张计算机存储字母”A”是1100001,而小王存储字母”A”是11000010,这样双方交换信息时就会误解。比如小张把1100001发送给小王,小王并不认为1100001是字母”A”,可能认为这是字母”X”,于是小王在用 访问存储在硬盘上的1100001时,在 上显示的就是字母”X”。也就是说,小张和小王使用了不同的编码表。小张用的编码表是ASCII,ASCII编码表把26个字母都一一的对应到2进制1和0上;小王用的编码表可能是EBCDIC,只不过EBCDIC编码与ASCII编码中的字母和01的对应关系不同。一般地说,开放的 (LINUX 、WINDOWS等)采用ASCII 编码,而大型 (MVS 、OS/390等)采用EBCDIC 编码。在发送数据给对方前,需要事先告知对方自己所使用的编码,或者通过 ,使不同编码方案的两个系统可沟通自如。
ASCII码使用7位2进制数表示一个字符,7位2进制数可以表示出2的7次方个字符,共128个字符。EBCDIC码使用8位,可以表示出2的8次方个字符,256个字符。
无论是ASCII码还是EBCDIC码,都无法对拥有几万个的汉字进行编码。因为上面已经提过,7位2进制数最多对应上128个字符,8位最多对应上256个字符。
0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(振铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8、9、10和13分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序而对文本显示有不同的影响。
32~126(共95个)是字符(32sp是空格),其中48~57为0到9十个阿拉伯数字,65~90为26个大写英文字母,97~122为26个小写字母,其余为一些标点符号、运算符号等。
编码发展历史
MBCS
为了扩充 ,以用于显示本国的语言,不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个 来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码,又称为"MBCS(Muilti-Bytes Charecter Set,多字节字符集)"。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文 下,ANSI 编码代表 JIS 编码,所以在中文 windows下要转码成gb2312,gbk只需要把文本保存为ANSI 编码即可。 不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。一个很大的缺点是,同一个编码值,在不同的编码体系里代表着不同的字。这样就容易造成混乱。导致了unicode码的诞生。
其中每个语言下的ANSI编码,都有一套一对一的编码转换器,Unicode变成所有编码转换的中间介质。所有的编码都有一个转换器可以转换到Unicode,而Unicode也可以转换到其他所有的编码。
GB2312
GB2312 也是ANSI编码里的一种,对ANSI编码最初始的 进行扩充,为了满足国内在计算机中使用汉字的需要,中国国家标准总局发布了一系列的汉字 国家标准编码,统称为GB码,或国标码。其中最有影响的是于1980年发布的《信息交换用汉字编码 基本集》,标准号为GB 2312-1980,因其使用非常普遍,也常被通称为国标码。GB2312编码通行于我国内地;新加坡等地也采用此编码。几乎所有的中文系统和国际化的软件都支持GB 2312。
GB 2312是一个 ,由6763个常用汉字和682个 的非汉字字符组成。其中汉字根据使用的频率分为两级。一级汉字3755个,二级汉字3008个。由于字符数量比较大,GB2312采用了二维矩阵编码法对所有字符进行编码。首先构造一个94行94列的方阵,对每一行称为一个“区”,每一列称为一个“位”,然后将所有字符依照下表的规律填写到方阵中。这样所有的字符在方阵中都有一个唯一的位置,这个位置可以用区号、位号合成表示,称为字符的区位码。如第一个汉字“啊”出现在第16区的第1位上,其区位码为1601。因为区位码同字符的位置是完全对应的,因此区位码同字符之间也是一一对应的。这样所有的字符都可通过其区位码转换为数字编码信息。GB2312字符的排列分布情况见表1-4。
表1-4 GB2312 字符编码分布表
分区范围 | 符号类型 |
第01区 | 中文标点、数学符号以及一些特殊字符 |
第02区 | 各种各样的数学序号 |
第03区 | 全角西文字符 |
第04区 | 日文平假名 |
第05区 | 日文片假名 |
第06区 | 希腊字母表 |
第07区 | 俄文字母表 |
第08区 | 中文拼音字母表 |
第09区 | 制表符号 |
第10-15区 | 无字符 |
第16-55区 | 一级汉字(以拼音字母排序) |
第56-87区 | 二级汉字(以部首笔画排序) |
第88-94区 | 无字符 |
GB2312字符在计算机中存储是以其区位码为基础的,其中汉字的区码和位码分别占一个存储单元,每个汉字占两个存储单元。由于区码和位码的取值范围都是在1-94之间,这样的范围同西文的存储表示冲突。例如汉字‘珀’在GB2312中的区位码为7174,其两 表示形式为71,74;而两个西文字符‘GJ’的存储码也是71,74。这种冲突将导致在解释编码时到底表示的是一个汉字还是两个西文字符将无法判断。
为避免同西文的存储发生冲突,GB2312字符在进行存储时,通过将原来的每个 第8bit设置为1同西文加以区别,如果第8bit为0,则表示西文字符,否则表示GB2312中的字符。实际存储时,采用了将区位码的每个 分别加上A0H(160)的方法转换为存储码,计算机存储规则是此编码的补码,而且是位码在前,区码在后。例如汉字‘啊’的区位码为1601,其存储码为B0A1H,其转换过程为:
区位码 | 区码转换 | 位码转换 | 存储码 |
1001H | 10H+A0H=B0H | 01H+A0H=A1H | B0A1H |
GB2312编码用两个 (8位2进制)表示一个汉字,所以理论上最多可以表示256×256=65536个汉字。但这种编码方式也仅仅在中国行得通,如果您的网页使用的GB2312编码,那么很多外国人在浏览你的网页时就可能无法正常显示,因为其 不支持GB2312编码。当然,中国人在浏览外国网页(比如日文)时,也会出现 或无法打开的情况,因为我们的 没有安装日文的编码表。
GBK
GBK即汉字内码扩展规范,K为扩展的汉语拼音中“扩”字的声母。英文全称Chinese Internal Code Specification。GBK编码标准兼容GB2312,共收录汉字21003个、符号883个,并提供1894个造字码位,简、繁体字融于一库。GB2312码是中华人民共和国国家汉字信息交换用编码,全称《信息交换用汉字编码 ——基本集》,1980年由国家标准总局发布。基本集共收入汉字6763个和非汉字图形字符682个,通行于中国大陆。新加坡等地也使用此编码。GBK是对GB2312-80的扩展,也就是CP936字码表 (Code Page 936)的扩展(之前CP936和GB 2312-80一模一样)。
基本简介
GB 2312的出现,基本满足了汉字的计算机处理需要,但对于人名、古汉语等方面出现的罕用字,GB 2312不能处理,这导致了后来GBK及GB 18030汉字 的出现。 采用双 表示,总体编码范围为8140-FEFE,首字节在81-FE 之间,尾字节在40-FE 之间,剔除 xx7F一条线。总计23940 个码位,共收入21886个汉字和图形符号,其中汉字(包括部首和构件)21003 个,图形符号883 个。P-Windows3.2和苹果OS以GB2312为基本 , Windows 95/98则以GBK为基本汉字编码。
有些汉字用 和拼音都打不出来,如:溙(五笔IDWI),须调出GBK 才能打出这个字。 中可右击 图标,设置,属性中选GBK 。 中可点击工具条中相关图标进行转换。
计算公式
GBK码对 中 的计算公式为:[(GBKH-0x81)*0xBE+(GBKL-0x41)]*(汉字离散后每个汉字 所占用的 )
编码方式
字符有一 和双字节编码,00–7F范围内是一位,和 保持一致,此范围内严格上说有96个字符和32个控制符号。之后的双 中,前一字节是双字节的第一位。总体上说第一 的范围是81–FE(也就是不含80和FF),第二字节的一部分领域在40–7E,其他领域在80–FE。
Big5
在台湾、香港与澳门地区,使用的是繁体中文 。而1980年发布的GB2312面向 ,并不支持繁体汉字。在这些使用繁体中文 的地区,一度出现过很多不同厂商提出的字符集编码,这些编码彼此互不兼容,造成了信息交流的困难。为统一繁体 编码,1984年,台湾五大厂商宏碁、神通、佳佳、零壹以及大众一同制定了一种繁体中文编码方案,因其来源被称为五大码,英文写作Big5,后来按英文翻译回汉字后,普遍被称为 。 是一种繁体中文汉字 ,其中繁体汉字13053个,808个标点符号、希腊字母及特殊符号。 的编码码表直接针对 而设计,每个字符统一使用两个 存储表示。第1 范围81H-FEH,避开了同ASCII码的冲突,第2字节范围是40H-7EH和A1H-FEH。因为Big5的字符编码范围同GB2312字符的存储码范围存在冲突,所以在同一正文不能对两种 的字符同时支持。
Big5编码的分布如表1-5所示,Big5字符主要部分集中在三个段内:标点符号、希腊字母及特殊符号;常用汉字;非常用汉字。其余部分保留给其他厂商支持。
表1-5 Big5字符编码分布表
编码范围 | 符号类别 |
8140H-A0FEH | 保留(用作造字区) |
A140H-A3BFH | 标点符号、希腊字母及特殊符号 |
A3C0H-A3FEH | 保留(未开放用于造字区) |
A440H-C67EH | 常用汉字(先按笔划,再按部首排序) |
C6A1H-C8FEH | 保留(用作造字区) |
C940H-F9D5H | 非常用汉字(先按笔划,再按部首排序) |
F9D6H-FEFEH | 保留(用作造字区) |
Big5编码推出后,得到了繁体中文软件厂商的广泛支持,在使用繁体汉字的地区迅速普及使用。目前,Big5编码在台湾、香港、澳门及其他海外华人中普遍使用,成为了繁体中文编码的事实标准。在互联网中检索繁体中文网站,所打开的网页中,大多都是通过Big5编码产生的文档。
Unicode
如上ANSI编码条例中所述,世界上存在着多种编码 UNICODE方式,在ANSi编码下,同一个编码值,在不同的编码体系里代表着不同的字。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文 下,ANSI 编码代表 JIS 编码,可能最终显示的是中文,也可能显示的是日文。在ANSI编码体系下,要想打开一个文本文件,不但要知道它的编码方式,还要安装有对应编码表,否则就可能无法读取或出现 。为什么 和网页都经常会出现 ,就是因为信息的提供者可能是日文的ANSI编码体系和信息的读取者可能是中文的编码体系,他们对同一个二进制编码值进行显示,采用了不同的编码,导致乱码。这个问题促使了unicode码的诞生。
如果有一种编码,将世界上所有的符号都纳入其中,无论是英文、日文、还是中文等,大家都使用这个 ,就不会出现编码不匹配现象。每个符号对应一个唯一的编码, 问题就不存在了。这就是Unicode编码。
Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,“汉”这个字的Unicode编码是U+6C49。
Unicode固然统一了编码方式,但是它的效率不高,比如UCS-4(Unicode的标准之一)规定用4个 一个符号,那么每个英文字母前都必然有三个字节是0,这对存储和传输来说都很耗资源。
UTF-8
为了提高Unicode的编码效率,于是就出现了UTF-8编码。UTF-8可以根据不同的符号自动选择编码的长短。比如英文字母可以只用1个 就够了。
UTF-8的编码是这样得出来的,以”汉”这个字为例:
“汉”字的Unicode编码是U+00006C49,然后把U+00006C49通过UTF-8编码器进行编码,最后输出的UTF-8编码是E6B189。
Base64
有的 (比如国外信箱)不支持非英文字母(比如汉字)传输,这是历史原因造成的(认为只有美国会使用电子邮件?)。因为一个英文字母使用ASCII编码来存储,占 的1个 (8位),实际上只用了7位2进制来存储,第一位并没有使用,设置为0,所以,这样的系统认为凡是第一位是1的字节都是错误的。而有的编码方案(比如GB2312)不但使用多个 编码一个字符,并且第一位经常是1,于是邮件系统就把1换成0,这样收到邮件的人就会发现邮件 。
为了能让 正常的收发信件,就需要把由其他编码存储的符号转换成ASCII码来传输。比如,在一端发送GB2312编码->根据Base64规则->转换成ASCII码,接收端收到ASCII码->根据Base64规则->还原到GB2312编码。
Python编码操作
编码encode()
解码decode()
电脑三大核心:cpu - 内存 - 硬盘(数据的存取过程)
软件及python解释器读取文件过程:启动 - 读取 - 展示|解释执行python2环境的文件头:# coding: 编码格式字符与字节
1. 字节的存储方式:8个二进制位
2. 字符所占字节数:根据编码的不同,所占字节数可能不同3. 三种格式字符串:u''、b''、r''u、b格式字符串转换
str(b'', encoding='utf-8')bytes(u'', encoding='utf-8')#例子:>>> str(b'python',encoding='utf-8')'python'>>> bytes(u'python', encoding='utf-8')b'python'
文件操作
1. 文件操作的三步骤:
打开文件 - 使用文件 - 关闭文件
2. 文件操作三要素:文件源、操作模式、编码
3. with语法:with open(...) as 别名, ..., open(...) as 别名: pass
4. 重点方法:read() | write() | readline() | close() | f.flush() | f.seek()
操作模式
模式 | 描述 |
---|---|
t | 文本模式 (默认)。 |
x | 写模式,新建一个文件,如果该文件已存在则会报错。 |
b | 二进制模式。 |
+ | 打开一个文件进行更新(可读可写)。 |
U | 通用换行模式(不推荐)。 |
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
操作方法
读操作
1.read()方法
# 文件1.txt的内容:hello friendcan you speak English!方法一:一次性读取全部f = open('1.txt','r',encoding='utf-8') #打开文件data = f.read() #读取文件f.close() #关闭文件#以上这种方法可能不太安全,如果我们忘记close这个操作,有时候就会发生文件内容出错的情况。那么,有没有什么方法可以避免这种问题呢?当然有!我们可以用下面这种方法:with open('1.txt', 'r', encoding='utf-8') as f: data = f.read() print(data)结果:hello friendcan you speak English!方法二:按行读取with open('1.txt', 'r', encoding='utf-8') as f: for data in f: print('1',data)结果:hello friendcan you speak English!
2.readline()
#readline每次只读一行with open('1.txt','r',encoding='utf-8') as f: data = f.readline() data2 = f.readline() print(data) print(data2)结果:hello friendcan you speak English!
3.readlines()
# readlines()返回的是一个列表,一行为一个元素with open('1.txt','r',encoding='utf-8') as f: data = f.readlines() print(data)结果:['hello friend','can you speak English!']
写操作
1.write()
f = open('2.txt','w',encoding='utf-8')f.write("hello friend\ncan you speak English!\n")f.close()# 同样的,我们也可以用with open这种方式with open('2.txt','w',encoding='utf-8') as f: f.write("hello friend\ncan you speak English!\n")
2.writelines()
# writelines()这个方法需要传递一个列表with open('3.txt','w',encoding='utf-8') as f: f.writelines(['123\n','456\n','789\n'])
3.flush()
#flush()方法的作用是:刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件, 而不是被动的等待输出缓冲区写入。f = open('2.txt','w',encoding='utf-8')f.write("hello friend\ncan you speak English!\n")f.flush()f.close()
光标
1.seek()方法
seek() 方法用于移动文件读取指针到指定位置。语法seek() 方法语法如下:fileObject.seek(offset[, whence])参数offset -- 开始的偏移量,也就是代表需要移动偏移的字节数whence:可选,默认值为 0。给offset参数一个定义,表示要从哪个位置开始偏移;0代表从文件开头开始算起,1代表从当前位置开始算起,2代表从文件末尾算起。# 打开文件f = open("3.txt", "r")# 重新设置文件读取指针到开头f.seek(0, 0)f.close()
2.tell()方法
#返回文件当前的位置