《老鸟python 系列》视频上线了,全网稀缺资源,涵盖python人工智能教程,爬虫教程,web教程,数据分析教程以及界面库和服务器教程,以及各个方向的主流实用项目,手把手带你从零开始进阶高手之路!点击 链接 查看详情




base64

阅读:227568525    分享到

我们知道在计算机中任何数据都是按字节存储的,有的复杂的数据(比如汉字)可能由多个字节表示,简单(比如单个英文字符)的由 1 个字节表示,字节是内存中基本的存储单位。

我们知道一个字节可表示的范围是 0 ~ 255(十六进制:0x00 ~ 0xFF),其中 ascii 值的范围为 0 ~ 127(十六进制:0x00 ~ 0x7F);而超越 ascii 范围的 128~255(十六进制:0x80 ~ 0xFF)之间的值是不可见字符。当然也并不是所有的 ascii 都是可见的,ascii 中只有 95 个可见字符(范围为 32 ~ 126),其余的 ascii 为不可见字符,本节后面我们会列出所有可见的 ascii 和 不可见的 ascii。

当不可见字节流在网络上传输时,比如说从 A 计算机传到 B 计算机,往往要经过多个路由设备,由于不同的设备(特指老的路由设备)对字节流的处理方式有一些不同,这样那些不可见字节就有可能被处理错误,这是不利于传输的。所以就先把数据先做一个 base64 编码,统统变成可见字节,也就是 ascii 码可表示的可见字符,确保数据可靠传输。base64 的内容是有 0 ~9,a ~z,A ~Z,+,/组成,正好 64 个字符,这些字符是在 ascii 可表示的范围内,属于 95 个可见字符的一部分。

对于现在的路由设备,只要是文本字符(无论是否是可见字符)都可以转为字节流(字节类型数据)在网络上传输。而字节类型的数据(图片,音频,视频,语音等二进制数据), 本身就是字节流,所以可以直接在网络上传输。但是,在企业开发中,我们为了一致性约定,往往对要发送的数据先进行序列化,然后再编码(encoding)为字节流在网络上发送,而对方收到数据后,要先把字节流解码(decoding)为字符串,然后再反序列化还原为原始数据。

我们往往使用 json 对要发送的数据进行序列化,因为 json 有多语言支持,而 pickle 只有 Python 语言本身支持,我们知道通信的另一方不一定也是使用 Python 语言,所以,我们对网络传输的数据一般不使用 pickle 进行序列化。然而,json 无法序列化字节类型的数据,那我们如果想发送字节类型的数据(图片,音频,视频,语音等二进制数据),应该怎么办呢?

有些同学可能会想,我们可以把字节类型数据转为字符串类型数据,然后再使用 json 序列化,然而不幸的是,对于字节类型的图片,音频,视频,语音等二进制数据里面含有很多不可见字节,而这些不可见字节无法转为字符串。但是,我们可以先用 base64 把这些不可见字节数据编码为可见字节数据,然后再转为字符串类型,然后在通过 json 进行序列化,然后再编码为字节流通过网络发送出去。而对方收到数据后,要先把字节流解码为字符串,然后再反序列化,然后再转为字节类型数据,然后再使用 base64 解码还原成原始数据。

最后,大家可能要问,字节类型的数据(图片,音频,视频,语音等二进制数据)本身就可以直接通过网络传输,为什么还要序列化?在此,我再告诉大家,你项目中要传输的数据不只有这些二进制吧,应该还需要包容其它类型的数据(int,list,dict等等),而这些结构化类型数据,必须要序列化后,才能通过网络传输。通信双方约定的就是发送的东西都是序列化后数据,这样方便对通信双方的数据类型进行还原。你没必要只对其它类型的数据序列化,对字节类型数据不序列化,这样反而增加了业务逻辑。

base64 的使用

base64 是 Python 内建模块,我们只需要 import 导入模块就可以使用,我们可以把超越 ascii 范围的字符用 base64 编码为可见字符,注意 base64只能编码字节类型数据。

import base64

mydata = b"\x80"  # \x80 换成十进制为 128,超越了 ascii 范围,为不可见字节
datab64 = base64.b64encode(mydata)  # 编码为 base64 的可见字节
print(datab64)

mydata = base64.b64decode(datab64)
print(mydata)  # 解码为不可见字节 \x80

base64 自己有一套算法可以把我们的字节编码成 ascii 范围内的字节,当然它不关心我们给的字节是不是可见字节,如果是可见字节同样要编码,如果是不可见字节则会编码成可见字节。

import base64

dataone = "p".encode("utf8")    # 可见字节
datatwo = "鸟".encode("utf8")   # 不可见字节

print(base64.b64encode(dataone))  # 编码可见字节为可见字节
print(base64.b64encode(datatwo))  # 编码不可见字节为可见字节

base64 编码会把 3 个字节的字节类型数据编码为 4 个可见字节的字节类型数据,如果要编码的字节类型数据不是 3 的倍数,最后会剩下 1 个或 2 个字节,base64 会在编码的末尾加上 1 个或 2 个 = 号,表示补了多少字节,解码的时候,会自动去掉。

import base64

strone = "p".encode("utf8")             # 1个字节
strtwo = "py".encode("utf8")            # 2 个字节
strthree = "pyt".encode("utf8")         # 3 个字节
strfour = "pyth".encode("utf8")         # 4 个字节
strfive = "pytho".encode("utf8")        # 5 个字节
strsix = "python".encode("utf8")        # 6 个字节
strseven = "老鸟".encode("utf8")        # utf-8 编码,每个汉字一般占 3 个字节
streight = "老鸟python".encode("utf8")  # utf-8 编码,每个汉字一般占 3 个字节,英文占 1 个字节

print(base64.b64encode(strone))    # 按 3 个字节(缺 2 个,补齐 2 个 =)编码成 4 个字节 cA==
print(base64.b64encode(strtwo))    # 按 3 个字节(缺 1 个,补齐 1 个 =)编码成 4 个字节 cHk=
print(base64.b64encode(strthree))  # 按 3 个字节编码成 4 个字节 cHl0
print(base64.b64encode(strfour))   # 按 6 个字节(缺 2 个,补齐 2 个 =)编码成 8 个字节 cHl0aA==
print(base64.b64encode(strfive))   # 按 6 个字节(缺 1 个,补齐 1 个 =)编码成 8 个字节 cHl0aG8=
print(base64.b64encode(strsix))    # 按 6 个字节编码成 8 个字节 cHl0aG9u
print(base64.b64encode(strseven))  # 按 6 个字节编码成 8 个字节 6ICB6bif
print(base64.b64encode(streight))  # 按 12 个字节编码成 16 个字节 6ICB6bifcHl0aG9u

我们可以对用 base64 编码后的字节进行解码。

import base64

dataone = "python".encode("utf8")    # 可见字节
datatwo = "老鸟".encode("utf8")       # 不可见字节

dataone = base64.b64encode(dataone)  # 编码可见字节 "python"
datatwo = base64.b64encode(datatwo)  # 编码不可见字节 "老鸟" 为可见字节

print(base64.b64decode(dataone))     # 解码 base64 编码后的字节
print(base64.b64decode(datatwo))     # 解码 base64 编码后的字节

由于 base64 编码后可能出现字符 + 和 /,在网页上传输数据,我们用 get 方式传输字符串时,字符 + 和 / 在 URL 中有特殊用途,就不能直接作为参数,所以又有一种 "url safe" 的 base64 编码,其实就是把字符 + 和 / 分别变成 - 和 _。

import base64

mydata = b"\xfb\xef\xff"
print(base64.b64encode(mydata))          # 编码为 ++//,但是 + 和 / 在 url 中有特殊用途,不能作为参数
print(base64.urlsafe_b64encode(mydata))  # 编码为 --__,很好。

在互联网上传输图片,音乐,视频,语音等等这些二进制数据是常用的需求, 本节开头部分,我们说过,最好要对所有发送的数据进行序列化后再进行网络传输。所以,正规的流程是:先用 base64 把这些不可见字节数据编码为可见字节数据,然后再转为字符串类型,然后在通过 json 进行序列化,然后再编码为字节流通过网络发送出去。而对方收到数据后,要先把字节流解码为字符串,然后再反序列化,然后再转为字节类型数据,然后再使用 base64 解码还原成原始数据。

import base64
import json

f = open("d:/test.png", "rb")  # 确保你计算机中存在 d:/test.png
imgdata = f.read()             # 二进制图片数据
f.close()

imgdatab64 = base64.b64encode(imgdata)     # 编码为可见字节
imgdatab64str = imgdatab64.decode("utf8")  # 转为字符串
jsondata = json.dumps(imgdatab64str)       # json 序列化
bytesdata = jsondata.encode("utf8")        # 编码为字节类型在网络上传输


'''
通过网络传输......,
对方机器收到后,先反序列化,最后用 base64 进行解码
'''

bytesdata_recv = bytesdata                     # 收到网络上发来的字节流
jsondata_recv = bytesdata_recv.decode("utf8")  # 解码为字符串
strdata_recv = json.loads(jsondata_recv)       # json 反序列化
bytesdata_recv = strdata_recv.encode("utf8")   # 转为字节类型数据
imgdata = base64.b64decode(bytesdata_recv)     # 解码为原始数据

# 把从网络上收到的图片数据写入磁盘,文件名字为 copy.png
f = open("d:/copy.png", "wb")
imgdata = f.write(imgdatab64)
f.close()

注意:base64 仅仅是把字节类型数据按照一定的算法转化为属于 ascii 范围内的可见字节类型数据,但不要作为加密行为。

ascii 可见和不可见字符表

我们知道一个字节的大小超越 ascii 编码的都是不可见字符,但 ascii 编码也不是所有字符都是可见的,在 128 个 ascii 中,只有 95 个可见字符,下表为 ascii 中可见字符表。

二进制 十进制 十六进制 图形
0010 0000 32 20 (空格)(␠)
0010 0001 33 21 !
0010 0010 34 22 "
0010 0011 35 23 #
0010 0100 36 24 $
0010 0101 37 25  %
0010 0110 38 26 &
0010 0111 39 27 '
0010 1000 40 28 (
0010 1001 41 29 )
0010 1010 42 2A *
0010 1011 43 2B +
0010 1100 44 2C ,
0010 1101 45 2D -
0010 1110 46 2E .
0010 1111 47 2F /
0011 0000 48 30 0
0011 0001 49 31 1
0011 0010 50 32 2
0011 0011 51 33 3
0011 0100 52 34 4
0011 0101 53 35 5
0011 0110 54 36 6
0011 0111 55 37 7
0011 1000 56 38 8
0011 1001 57 39 9
0011 1010 58 3A :
0011 1011 59 3B ;
0011 1100 60 3C <
0011 1101 61 3D =
0011 1110 62 3E >
0011 1111 63 3F ?
 
二进制 十进制 十六进制 图形
0100 0000 64 40 @
0100 0001 65 41 A
0100 0010 66 42 B
0100 0011 67 43 C
0100 0100 68 44 D
0100 0101 69 45 E
0100 0110 70 46 F
0100 0111 71 47 G
0100 1000 72 48 H
0100 1001 73 49 I
0100 1010 74 4A J
0100 1011 75 4B K
0100 1100 76 4C L
0100 1101 77 4D M
0100 1110 78 4E N
0100 1111 79 4F O
0101 0000 80 50 P
0101 0001 81 51 Q
0101 0010 82 52 R
0101 0011 83 53 S
0101 0100 84 54 T
0101 0101 85 55 U
0101 0110 86 56 V
0101 0111 87 57 W
0101 1000 88 58 X
0101 1001 89 59 Y
0101 1010 90 5A Z
0101 1011 91 5B [
0101 1100 92 5C \
0101 1101 93 5D ]
0101 1110 94 5E ^
0101 1111 95 5F _
 
二进制 十进制 十六进制 图形
0110 0000 96 60 `
0110 0001 97 61 a
0110 0010 98 62 b
0110 0011 99 63 c
0110 0100 100 64 d
0110 0101 101 65 e
0110 0110 102 66 f
0110 0111 103 67 g
0110 1000 104 68 h
0110 1001 105 69 i
0110 1010 106 6A j
0110 1011 107 6B k
0110 1100 108 6C l
0110 1101 109 6D m
0110 1110 110 6E n
0110 1111 111 6F o
0111 0000 112 70 p
0111 0001 113 71 q
0111 0010 114 72 r
0111 0011 115 73 s
0111 0100 116 74 t
0111 0101 117 75 u
0111 0110 118 76 v
0111 0111 119 77 w
0111 1000 120 78 x
0111 1001 121 79 y
0111 1010 122 7A z
0111 1011 123 7B {
0111 1100 124 7C |
0111 1101 125 7D }
0111 1110 126 7E ~

下表为 ascii 不可见字符表。

二进制 十进制 十六进制 缩写 可以显示的表示法 名称/意义
0000 0000 0 00 NUL 空字符(Null)
0000 0001 1 01 SOH 标题开始
0000 0010 2 02 STX 本文开始
0000 0011 3 03 ETX 本文结束
0000 0100 4 04 EOT 传输结束
0000 0101 5 05 ENQ 请求
0000 0110 6 06 ACK 确认回应
0000 0111 7 07 BEL 响铃
0000 1000 8 08 BS 退格
0000 1001 9 09 HT 水平定位符号
0000 1010 10 0A LF 换行键
0000 1011 11 0B VT 垂直定位符号
0000 1100 12 0C FF 换页键
0000 1101 13 0D CR 归位键
0000 1110 14 0E SO 取消变换(Shift out)
0000 1111 15 0F SI 启用变换(Shift in)
0001 0000 16 10 DLE 跳出数据通讯
0001 0001 17 11 DC1 设备控制一(XON 启用软件速度控制)
0001 0010 18 12 DC2 设备控制二
0001 0011 19 13 DC3 设备控制三(XOFF 停用软件速度控制)
0001 0100 20 14 DC4 设备控制四
0001 0101 21 15 NAK 确认失败回应
0001 0110 22 16 SYN 同步用暂停
0001 0111 23 17 ETB 区块传输结束
0001 1000 24 18 CAN 取消
0001 1001 25 19 EM 连接介质中断
0001 1010 26 1A SUB 替换
0001 1011 27 1B ESC 跳出
0001 1100 28 1C FS 文件分割符
0001 1101 29 1D GS 组群分隔符
0001 1110 30 1E RS 记录分隔符
0001 1111 31 1F US 单元分隔符
0111 1111 127 7F DEL 删除

本节重要知识点

会使用 base64 进行编解码。

清楚 base64 使用的场景。

作业

我们在 IO 编程中知道,因为图片数据有大量不可见字节,如果要把图片数据保存到磁盘上,需要用 "wb" 的方式写文件。如果我们把图片数据用 base64 编码成可见字节,然后在转为字符串,最后用 "w" 的形式存储到磁盘上。读数据的时候,先把读出的字符串转为字节类型数据,然后用 bases64 解码还原,这样也是可以的,请编码完成这个试验。


如果以上内容对您有帮助,请老板用微信扫一下赞赏码,赞赏后加微信号 birdpython 领取免费视频。


登录后评论

user_image
lll
2020年4月3日 18:15 回复

一个字节的内容如果不能用 ascii 编码表示的话,那就是不可见字符


user_image
lll
2020年4月3日 18:06 回复

二进制根本不需要序列化,二进制数据不是代码, 不包含 代码里面的 数据类型,对象 根本无序列化的必要,base64更加不需要


user_image
fairy-lee
2020年3月8日 16:56 回复

写的很清楚,赞一个~~


user_image
司徒正美
2019年12月12日 15:36 回复

终于弄明白base64的应用场景了,谢谢老鸟~~


user_image
王二小
2019年7月27日 01:07 回复

博主的代码非常有用


user_image
风月
2019年2月26日 13:39 回复

如果是将一个文件进行base 64编码,那怎么对其解码呢,在不知道原文件格式的情况下