字符串编解码随笔

编码解码

首先,计算机的底层是只能接收由01组成的比特来处理信息的(如:01001011),我们一般普通的人类很少能看懂这些纯粹的01串的,我们比较容易看懂我们熟悉的字符(比如abcdefg)。为了使得计算机既能正确的运行,同时我们人类也能方便地进行阅读与编写,于是我们约定好一套映射规则(字符->比特),某些字符直接映射为某串比特,同时某串比特也能反过来映射为某字符,即数学上的一一映射。

比如,我规定了“我”这个汉字就对应着’01’,“你”这个汉字就对应着’10’,“爱”这个汉字就对应着’11’,那么’011110’这一串就代表了“我爱你”3个字。

将某字符映射为某串比特的这个过程称为编码,而将某串比特映射为某字符的这个过程称为解码

字符集

这个世界的字符有很多,人们将其中某一些相似的或最常用的符号或文字汇总起来的集合,就叫做字符集。不同的分类方法形成不同的字符集。

ASCII码字符集

其中最早的字符集就是ASCII码字符集,它规定了数字,大小写英文字符,以及各种常用的符号共128个作为字符集合。

序号缩写/字符解释编码
0NUL(null)空字符0x00
1SOH(start of headline)标题开始0x01
10LF (NL line feed, new line)换行键0x0A
480字符00x30
65A大写字母A0x41
97a小写字母a0x61
127DEL (delete)删除0x7F

由于这128个字符只需要用一个字节(8位比特)就能完全表示了,所以一个ASCII码占用1个字节。

字符集是有范围的,一旦超出了,计算机就无法识别这串比特到底是代表了什么字符。比如想表示中文的时候,ASCII码就无法表示了,因为中文不在这个集合里。
这时候就需要扩展字符集,其中,英文扩展的是ISO8859-1 字符集,也就是 Latin-1,是西欧常用字符,包括德法两国的字母。
中文扩展有GB2312,GBK,BIG5等字符集。
而众多扩展中,最常见的应用最广泛的就数Unicode字符集了。

注意:字符集仅仅是一个集合,或者说是只是一个有序集,不是编码方式。用数学的话来表示,那就是{a , b , c , … , A , B , C , …}以及它们对应的序号。而将字符集如何映射为比特(比如A->0x41)才是编码方式,虽然字符A的序号65的十六进制写法0x41跟它(字符A)的编码0x41相同,但它们所指的其实并不是同一个概念,只是因为ASCII码的编码方式是直接使用序号的进制转换罢了,所以感觉没什么区别。但这一点差异在Unicode字符集里明显体现出来,也是很多人经常搞错的2个概念。

Unicode字符集

Unicode字符集编码是Universal Multiple-Octet Coded Character Set
通用多八位编码字符集的简称,是由一个名为 Unicode 学术学会(Unicode
Consortium)的机构制订的字符编码系统,支持现今世界各种不同语言的书面文本的交换、处理及显示。该编码于1990年开始研发,1994年正式公布,最新版本是2019年5月7日的Unicode

Unicode字符集的编码方式:
上面我已经说过,字符集其实就是一个有序集合,所以一个字符的Unicode序号其实都是唯一确定好了的。但是怎么对其进行编码却有不同的方法,因为涉及多字节编码时,就有一个大尾存储和小尾存储的差异(这里就不详细说什么是大尾存储和小尾存储了,直接看代码结果即可体验它们之间的差异),以及存储空间浪费问题。
Unicode字符集大体可以分成3种编码方式:UTF-8、UTF-16、UTF-32(Unicode Transformation Format,简称为UTF),三种编码方式会分别将同一个Unicode字符映射为三种不同的比特串。

直接上代码:
class Data_encode():
    def data_joinord(self, data):
        lin = ['%02X' % ord(i) for i in data]
        data = " ".join(lin)
        return data

    def data_join(self, data):
        lin = ['%02X' % i for i in data]
        data = " ".join(lin)
        return data

    def data_encode(self, data):
        # print('ord:',self.data_joinord(data))
        print('ord: %-30s' %(self.data_joinord(data)))

        print('UTF-8:',self.data_join(data.encode('UTF-8')))
        print('UTF-16:',self.data_join(data.encode('UTF-16')))
        print('utf_16_le:',self.data_join(data.encode('utf_16_le')))
        print('utf_16_be:',self.data_join(data.encode('utf_16_be')))
        print('UTF-32:',self.data_join(data.encode('UTF-32')))

        print('unicode_escape:',data.encode('unicode_escape'))

        print('gbk:',self.data_join(data.encode('gbk')))
        



# str = 'ABC'
# str = 'A'
de = Data_encode()

s = de.data_encode('A')
print("--------------------------------")
s = de.data_encode('AB')
print("--------------------------------")
s = de.data_encode('编')
print("--------------------------------")
s = de.data_encode('编码')
# s = de.data_encode('码')
# s = de.data_encode('解')
运行结果:
ord: 41                            
UTF-8: 41
UTF-16: FF FE 41 00
utf_16_le: 41 00
utf_16_be: 00 41
UTF-32: FF FE 00 00 41 00 00 00
unicode_escape: b'A'
gbk: 41
--------------------------------
ord: 41 42                         
UTF-8: 41 42
UTF-16: FF FE 41 00 42 00
utf_16_le: 41 00 42 00
utf_16_be: 00 41 00 42
UTF-32: FF FE 00 00 41 00 00 00 42 00 00 00
unicode_escape: b'AB'
gbk: 41 42
--------------------------------
ord: 7F16                          
UTF-8: E7 BC 96
UTF-16: FF FE 16 7F
utf_16_le: 16 7F
utf_16_be: 7F 16
UTF-32: FF FE 00 00 16 7F 00 00
unicode_escape: b'\\u7f16'
gbk: B1 E0
--------------------------------
ord: 7F16 7801                     
UTF-8: E7 BC 96 E7 A0 81
UTF-16: FF FE 16 7F 01 78
utf_16_le: 16 7F 01 78
utf_16_be: 7F 16 78 01
UTF-32: FF FE 00 00 16 7F 00 00 01 78 00 00
unicode_escape: b'\\u7f16\\u7801'
gbk: B1 E0 C2 EB
解释:

上面代码分别用英文字符’A’,英文字符串’AB’,中文字符’编’,以及中文字符串’编码’进行演示
'编’字的Unicode字符集数值(或者说序号)是: 7F16
UTF-8: E7 BC 96,UTF-8使用3个字节来对’编’字进行编码映射成比特串(E7BC96)
UTF-16: FF FE 16 7F,UTF-16使用了2+2个字节对’编’字进行编码映射成比特串(FFFE167F)

我这里为什么要说2+2个字节而不是直接说4个字节?因为只有开头的2个字节是为了说明这次编码是用小尾存储格式,之后跟着的所有字符都是使用每2个字节进行编码。

utf_16_le: 16 7F,utf_16_le是直接说明使用小尾存储进行,所以只用2个字节(167F)来表示’编’字
这里注意到,小尾存储时,小尾的编码(167F)跟Unicode字符集数值7F16的高8位与低8位刚好是相反的。
utf_16_be: 7F 16,utf_16_be是直接说明使用大尾存储进行,用2个字节(7F16)来表示’编’字。
后面的演示就省略了,对比看看就能发现不同编码之间的差异。

结论

所以不要再混淆Unicode,UTF-8,UTF-16这几个概念了
经常说的Unicode其实只是一个字符集,一个有序集合,是一个集合
UTF-8是编码,是对Unicode字符如何映射成比特的具体实现方案,也指最终的映射结果,最终的这一串比特串。
而说某个字符的“Unicode码”,其实是指这个字符在集合里的序号,虽然这个序号并非一定要是个整数。

Unicode其实也分有多个标准,如UCS-2,UCS-4等,这里不细说了

乱码

某个字符,如果由字符集a进行编码,那么原则上只有通过字符集a或者兼容字符集a的字符集A才能对其进行解码。如果在解码时用的是字符集b而不是用字符集a(或兼容字符集A),那么就会出现一些看不懂的字符。
比如说:

字符:"我"字,使用UTF-8编码,对应的二进制数据(缩写成16进制)是E6 88 91
此时如果要使用GBK进行解码,由于GBK是使用2字节变长编码方式进行的,解码时它会先取前2字节E6 88,
找到了对应的字符是"鎴",然后继续解码91,由于此时超出ascii码范围(00-7F),无法解码成正常字符。
所以看到的是:"鎴�"
这就是乱码的由来。

上代码查看检验:

s = de.data_encode('我')
print("--------------------------------")
s = de.data_encode('鎴')

运行结果:

ord: 6211                          
UTF-8: E6 88 91
UTF-16: FF FE 11 62
utf_16_le: 11 62
utf_16_be: 62 11
UTF-32: FF FE 00 00 11 62 00 00
unicode_escape: b'\\u6211'
gbk: CE D2
--------------------------------
ord: 93B4                          
UTF-8: E9 8E B4
UTF-16: FF FE B4 93
utf_16_le: B4 93
utf_16_be: 93 B4
UTF-32: FF FE 00 00 B4 93 00 00
unicode_escape: b'\\u93b4'
gbk: E6 88

可见,“鎴”字的GBK编码确实是E6 88,也是“我”字的UTF-8编码前2个字节。
解码时的字符集不匹配,或者二进制数据残缺、损坏等都会导致乱码。

base64

未完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值