Python 编码最全梳理-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

为什么要写这篇文章呢这里就要提到某一天工作的时候突然发现自己在编码方面一窍不通。实在惭愧

字符编码是计算机技术的基石对于程序员来说尤其重要字符编码的知识是必须要懂的

编码入门知识

ASCII 码计算机内部所有信息最终都是一个二进制值。每一个二进制位bit有0和1两种状态因此八个二进制位就可以组合出256种状态这被称为一个字节byte。也就是说一个字节一共可以用来表示256种不同的状态每一个状态对应一个符号就是256个符号从00000000到11111111。上个世纪60年代美国制定了一套字符编码对英语字符与二进制位之间的关系做了统一规定。这被称为 ASCII 码一直沿用至今。ASCII 码一共规定了128个字符的编码比如空格SPACE是32二进制00100000大写的字母A是65二进制01000001。这128个符号包括32个不能打印出来的控制符号只占用了一个字节的后面7位最前面的一位统一规定为0
在这里插入图片描述

非ASCII 码编码英语用128个符号编码就够了但是用来表示其他语言128个符号是不够的。比如在法语中字母上方有注音符号它就无法用 ASCII 码表示。于是一些欧洲国家就决定利用字节中闲置的最高位编入新的符号。比如法语中的é的编码为130二进制10000010。这样一来这些欧洲国家使用的编码体系可以表示最多256个符号
至于亚洲国家的文字使用的符号就更多了汉字就多达10万左右。一个字节只能表示256种符号肯定是不够的就必须使用多个字节表达一个符号。比如简体中文常见的编码方式是 GB2312使用两个字节表示一个汉字所以理论上最多可以表示 256 x 256 = 65536 个符号
世界上存在着多种编码方式同一个二进制数字可以被解释成不同的符号。因此要想打开一个文本文件就必须知道它的编码方式否则用错误的编码方式解读就会出现乱码。为什么电子邮件常常出现乱码就是因为发信人和收信人使用的编码方式不一样

UnicodeUnicode 当然是一个很大的集合现在的规模可以容纳100多万个符号。每个符号的编码都不一样比如U+0639表示阿拉伯字母AinU+0041表示英语的大写字母AU+4E25表示汉字严。具体的符号对应表可以查询unicode.org或者专门的汉字对应表。Unicode 只是一个符号集它只规定了符号的二进制代码却没有规定这个二进制代码应该如何存储。
比如汉字严的 Unicode 是十六进制数4E25转换成二进制数足足有15位100111000100101也就是说这个符号的表示至少需要2个字节。表示其他更大的符号可能需要3个字节或者4个字节甚至更多
这里就有两个严重的问题第一个问题是如何才能区别 Unicode 和 ASCII 计算机怎么知道三个字节表示一个符号而不是分别表示三个符号呢第二个问题是我们已经知道英文字母只用一个字节表示就够了如果 Unicode 统一规定每个符号用三个或四个字节表示那么每个英文字母前都必然有二到三个字节是0这对于存储来说是极大的浪费文本文件的大小会因此大出二三倍这是无法接受的。
它们造成的结果是1出现了 Unicode 的多种存储方式也就是说有许多种不同的二进制格式可以用来表示 Unicode。2Unicode 在很长一段时间内无法推广直到互联网的出现

UTF-8/16/32UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16字符用两个字节或四个字节表示和 UTF-32字符用四个字节表示不过在互联网上基本不用。重复一遍这里的关系是UTF-8 是 Unicode 的实现方式之一。UTF-8 最大的一个特点就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号根据不同的符号而变化字节长度

UTF-8 的编码规则很简单只有二条
1对于单字节的符号字节的第一位设为0后面7位为这个符号的 Unicode 码。因此对于英语字母UTF-8 编码和 ASCII 码是相同的
2对于n字节的符号n > 1第一个字节的前n位都设为1第n + 1位设为0后面字节的前两位一律设为10。剩下的没有提及的二进制位全部为这个符号的 Unicode 码。
在这里插入图片描述

跟据上表解读 UTF-8 编码非常简单。如果一个字节的第一位是0则这个字节单独就是一个字符如果第一位是1则连续有多少个1就表示当前字符占用多少个字节。
下面还是以汉字严为例演示如何实现 UTF-8 编码。
严的 Unicode 是4E25100111000100101根据上表可以发现4E25处在第三行的范围内0000 0800 - 0000 FFFF因此严的 UTF-8 编码需要三个字节即格式是1110xxxx 10xxxxxx 10xxxxxx。然后从严的最后一个二进制位开始依次从后向前填入格式中的x多出的位补0。这样就得到了严的 UTF-8 编码是11100100 10111000 10100101转换成十六进制就是E4B8A5

GB 18030(系列)与UTF-8相同采用多字节编码每个字可以由1个、2个或4个字节组成
编码空间庞大最多可定义161万个字符
支持中国国内少数民族的文字不需要动用造字区
汉字收录范围包含繁体汉字以及日韩汉字

BIG5字符集&编码Big5又称为大五码或五大码是使用繁体中文正体中文社区中最常用的电脑汉字字符集标准共收录13,060个汉字

1ANSI是默认的编码方式对于英文文件是ASCII编码对于简体中文文件是GB2312编码只针对 Windows 简体中文版如果是繁体中文版会采用 Big5 码
2Unicode编码这里指的是notepad.exe使用的 UCS-2 编码方式即直接用两个字节存入字符的 Unicode 码这个选项用的 little endian 格式
3Unicode big endian编码与上一个选项相对应
4UTF-8编码

即GBK、GB2312等与UTF8之间都必须通过Unicode编码才能相互转换

1GBK、GB2312 --先转–> Unicode --再转–> UTF8

2UTF8 --先转–> Unicode --再转–> GBK、GB2312

Base64 编码

现有的字符集非常多, 常用的有 UTF-8 / GBK 等这里面的某些字节在某些传输渠道不支持比如邮件传输不支持上面ASCII码的控制字符Base64 的创建就是为了解决此问题

Base64就是为了解决各系统以及传输协议中二进制不兼容的问题而生的

Base64有64个字符, 2^6 = 64, 所以每个Base64编码字符可以用一个6位的二进制来表示.
在这里插入图片描述

使用 Base64 进行编码大致可以分为 4 步

  1. 将原始数据每三个字节作为一组每个字节是8个bit所以一共是 24 个 bit
  2. 将 24 个 bit 分为四组每组 6 个 bit
  3. 在每组前面加补 00将其补全成四组8个bit
    到此步原生数据的3个字节已经变成4个字节了增大了将近30%
  4. 根据Base64码表得到扩展后每个字节的对应符号

在这里插入图片描述
在这里插入图片描述
有时我们会在Base64字符末尾会看到=有时1个有时2个这是为啥

通过上面的我们知道了Base64编码过程是3个字符一组的进行如果原文长度不是3的倍数怎么办呢 例如我们的原文为Ma它不够3个那么只能在编码后的字符串中补=了。缺一个字符补一个缺两个补两个即可所以有时候你会看见base64字符串结尾有1个或者2个=。

Python 2.x

Python 2.x 版本中默认的编码方式是 ASCII

所以在Python 2.x 版本当中如果需要加中文注释都需要添加以下代码以进行编码声明

# -*- coding: utf-8 -*-

json 编码注意事项需要显示的添加encoding='utf-8'并且设置添加ensure_ascii=False参数

import json
all_res = {}
write_path = "test.json"
all_res["名字"] = "段誉"

with open(write_path, "w", encoding='utf-8') as f:
    json.dump(all_res, f, ensure_ascii=False)

# test.json 内容
{"名字": "段誉"}

Python 3.x

Python 3.x 版本中字符串默认使用unicode 编码

和前面提到一样unicode是中间编码任何字符编码之前的转换都必须解码成unicode再编码成目标字符编码
在这里插入图片描述

json 编码注意事项需要显示的设置添加ensure_ascii=False参数

如果dict包含有汉字一定加上ensure_ascii=False。否则按参数默认值True意思是保证dumps之后的结果里所有的字符都能够被ascii表示汉字在ascii的字符集里面因此经过dumps以后的str里汉字会变成对应的unicode

import json
all_res = {}
write_path = "test.json"
all_res["名字"] = "段誉"

with open(write_path, "w") as f:
    json.dump(all_res, f, ensure_ascii=False)

# test.json 内容
{"名字": "段誉"}

总结

任何编码转换之前都要解码成unicode再转换到目标编码

u 开头的是unicode 编码

b 开头的是byte

Python3的str 默认不是bytes所以不能decode只能先encode转为bytes再decode

Python2的str 默认是bytes所以能decode

Python 2.x 的编码注意规则

# -*- coding: utf-8 -*-
utf_8_a = '中文'
gbk_a = utf_8_a.decode('utf-8').encode('gbk')
print(gbk_a.decode('gbk'))
 
#输出结果 中文

疑问例子

>>> a = {"data" : "你是谁\035我是段誉"}
>>> a # # 在输出字典 a 时Python 使用\x1d 来表示\035这是十六进制表示
{'data': '你是谁\x1d我是段誉'} 
>>> b = json.dumps(a)
>>> b # 其中 \035 => unicode 编码为 \\u001d
'{"data": "\\u4f60\\u662f\\u8c01\\u001d\\u6211\\u662f\\u6bb5\\u8a89"}'
>>> c = json.dumps(a, ensure_ascii=False)
>>> c
'{"data": "你是谁\\u001d我是段誉"}'
>>> e.encode("utf-8").decode("unicode_escape")
'{"data": "你是谁4我是段誉"}'

解答一变量bjson.dumps 成str 之后无法转回中文

unicode_escape 将unicode 的内存编码进行存储读取文件的时候反向转换回来直接将unicode 编码进行转换

解答二\035 的问题

对于 \035 编码问题的解释\035 在 ASCII 码中如图所示8 进制表示\035\x1d 表示16 进制属于不可见字符 < 32 都属于不可见字符

当你使用 json.dumps(a) 对字典进行 JSON 编码时默认情况下Python 将特殊字符进行 Unicode 转义以确保生成的 JSON 字符串是 ASCII 可读的。这就是为什么在输出的 JSON 字符串中看到 \u001d它是 Unicode 编码的表示形式对应分组符
在这里插入图片描述

但是为什么以上例子中c变量还是输出\u001d因为特殊字符’\035’属于不可见字符ASCII 码 < 32所以仍然会转成unicode编码\u001d这是json 规范的一部分确保生成的json 字符串是有效的可移植的。

如果这个符号是\064->4的话那就是c=“{“data”: “你是谁4我是段誉”}”

即以为在media_type 为json 时候用来告诉服务端消息主体是序列化后的JSON字符串默认会转unicode 编码

media_type 为 plain 时候纯文本格式空格转换为 “+” 加号但不对特殊字符编码

参考
https://zhuanlan.zhihu.com/p/38333902
https://zhuanlan.zhihu.com/p/384238870
https://blog.csdn.net/j550341130/article/details/100046887

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: python