base64编码的实现
什么是base64
base64是对数据进行编码的方式之一,是最基础的8bit字节码的编码方式。
基本原理(以ASCII为例):
1、将数据按每3个字节断开( 3 * 8bit = 24,后面会将24以6bit的形式重组,再将6bit填0补充为8bit)
2、获取要传输的数据中每个字节的ASCII编码
3、将3个字节的ASCII编码转换为二进制,一共24bit
4、将24bit每6个拆分为一组,组成4组
5、再对4组6bit数,在左侧补0,补充为8bit数。此步可省略
6、以新生成的二进制数所对应的十进制数为索引
7、通过索引值和Base64编码表,将每个字节的数据替换为编码后的数据
8、对数据最后不足3个字节的数据,补充 0x00(\x00)到3个字节
9、对最后替换后的编码数据,补了几个 \x00 ,就替换最后几个数据为 ==
末尾的处理:末尾可能出现不足3个字节的情况,不足则补0 补至3个字节,补0的字节要用 “=” 表示
进制转换关系
十进制转其他进制都是除对应进制的数字,除到0后将所有余数从下向上拼接成一个从左到右的数字,此数字即为对应进制中所代表的数字
其他进制转十进制都是按从右到左的位置,对应位置上的数乘进制数的从0~n次方(取决于有几位数),再将乘出来的结果相加,即为十进制中对应的结果
二进制 —— 0、1组合构成,8bit即为一个字符由8个0、1为一组表示。如 10010110 代表
- 1 —— 1 * 27 = 128
- 0 —— 0 * 26 = 0
- 0 —— 0 * 25 = 0
- 1 —— 1 * 24 = 16
- 0 —— 0 * 23 = 0
- 1 —— 1 * 22 = 4
- 1 —— 1 * 21 = 2
- 0 —— 0 * 20 = 0
转换为10进制则为 128 + 0 + 0 + 16 + 0 + 4 + 2 + 0 = 150
八进制 —— 以 0o 开头,不可省略,后方数字以满8进一进行计算。8进制数字从右到左分别代表 80、81、82、83…,如:0o24 则代表
- 0o - 八进制标识
- 1 - 2 * 81 = 16
- 4 - 4 * 80 = 4
转换为10进制则为 16 + 4 = 20
十六进制 —— 以 0x 开头,不可省略,后方数字以满16进一进行计算。16进制数字从右到左分别代表 160、161、162…,如:0x61则代表
- 0x —— 十六进制标识
- 6 —— 6 * 161 = 96
- 1 —— 1 * 160 = 1
转换为10进制则为 96 +1 = 97
十进制转二进制
十进制数 97 转二进制
- 97 / 2 —— 48 ··· 1
- 48 / 2 —— 24 ··· 0
- 24 / 2 —— 12 ··· 0
- 12 / 2 —— 06 ··· 0
- 06 / 2 —— 03 ··· 0
- 03 / 2 —— 01 ··· 1
- 01 / 2 —— 00 ··· 1
自下向上组合数字,不满8位则在左侧以0填充 —— 01100001
python中可以直接使用 bin() 方法将十进制转换为二进制
十进制转八进制
十进制数 97 转八进制
- 97 / 8 —— 12 ··· 1
- 12 / 8 —— 01 ··· 4
- 01 / 8 —— 00 ··· 1
转换为八进制后结果为,从下向上拼接一个自左向右的数字 —— 0o141
python中可以直接使用 oct() 方法将十进制转换为十六进制
十进制转十六进制
十进制数 97 转十六进制
- 97 / 16 —— 06 ··· 1
- 06 / 16 —— 00 ··· 6
转换为十六进制结果为 —— 0x61
python中可以直接使用 hex() 方法将十进制转换为十六进制
其他进制转10进制
借助 int(对应进制字符, 进制数) 转换为10进制
如:
16进制转10进制
十六进制转二进制
十六进制 | 二进制 | 十六进制 | 二进制 | 十六进制 | 二进制 | 十六进制 | 二进制 |
---|---|---|---|---|---|---|---|
0 | 0000 | 1 | 0001 | 2 | 0010 | 3 | 0011 |
4 | 0100 | 5 | 0101 | 6 | 0110 | 7 | 0111 |
8 | 1000 | 9 | 1001 | a | 1010 | b | 1011 |
c | 1100 | d | 1101 | e | 1110 | f | 1111 |
关于数据在内存中的大小端模式
大小端模式是两种数据在内存中存储的不同模式。
数据的高低位,如一个十六进制数据 0x12345678,自左向右为从 高位 到 低位
- 0x12 —— 高位
- 0x34 —— ↓
- 0x56 —— ↓
- 0x78 —— 低位
内存的高低地址,如下
- 0x1000 —— 低地址
- 0x1001 —— ↓
- 0x1002 —— ↓
- 0x1003 —— 高地址
大小端模式:
内存地址 | 大端模式 | 小端模式 |
---|---|---|
0x1000 | 0x12 | 0x78 |
0x1001 | 0x34 | 0x56 |
0x1002 | 0x56 | 0x34 |
0x1003 | 0x78 | 0x12 |
即大端模式将数据的高位存放到内存的低地址,小端模式将数据的高位存放到内存的高地址
大端模式是按照从左到右的顺序存储数据,更符合人的阅读和数据的网络传输(TCP协议规定)
Python将字节码(bytes)转换为数字
调用方法: int.from_bytes(bytesData, ‘big’ / ‘little’)
将字节码转换后的数字转换为十六进制的方法:hex()
python3中将字符转为字节码的方式 —> ‘abc’.encode()
如:
小端模式如下:
位运算符
运算符 | 含义 |
---|---|
& | 位与 |
l | 位或 |
~ | 取反 |
^ | 亦或 |
<< | 左移位 |
>> | 右移位 |
python解释器会自动将乘法、除法等运算自动的转换为移位运算,所以在python中移位运算符相比于其他运算符并没有明显的效率优势
原码、反码、补码
三码的出现是为了解决计算机中的减法运算(计算机的运算器只有加法),通过补码的方式来进行减法运算
原码:直接将其他进制转化为二进制的形式
反码:
- 正数 —— 按原码按位取反
- 负数 —— 符号位不变,其余位置按位取反
补码:
- 正数 —— 其原码本身
- 负数 —— 符号位不变,其余位置按位取反后 + 1。另外一个求补码的方法,符号位不变,负数中由低位向高位找到第一个1,保留该为的1及更低位的所有0,对其余位置求反。
如 -2,法一 :
- 原码 —— 1000 0010
- 反码 —— 1111 1101
- 补码 —— 1111 1110
法二:
- 原码 —— 1000 0010
- 补码 —— 1111 1110
实例1、 6 - 2 —> 6 + ( -2 ):
原码:
- 6 —— 0000 0110
- -2 —— 1000 0010
补码:
- 6 —— 0000 0110
- -2 —— 1111 1110
运算:
运算实例
运算时对应位置上数字根据运算符取与、或等,得出结果再转化为10进制
& —— 位与
例: 6 & 4 —> 4
解析:
- 6 —— 0b0110
- 4 —— 0b0100
6 | 4 —> 0b0110 | 0b0100
- 0b0110
- 0b0100
- 0b0100 —— 4
所以结果为4
| —— 位或
例: 1 | 2 —> 3
解析:
- 1 —— 0b0001
- 2 —— 0b0010
1 | 2 —> 0b0001 | 0b0010
- 0b0001
- 0b0010
- 0b0011 —— 3
所以结果为3
~ —— 取反
例: ~12
解析:
- 12 —— 0000 0110
>>、<< —— 右移位、左移位
如其名,向左右移动二进制数所在的位置
下例,均为python3.7中的移位运算结果,不全具有通用性
正数 5:
- 5 —— 0000 0101
- 5 >> 1 —— 0000 0010 —— 2
- 5 << 1 —— 0000 1010 —— 10
负数 -5:
- -5 —— 1000 0101
- -5 >> 1 —— 1000 0011 —— -3
- -5 << 1 —— 1000 1010 —— -10
最终代码
alphabet = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
def myBase64(src):
# 最终返回的二进制字节码序列
res = bytearray()
# 获取输入的字符的字节长度,判断要从哪断开
length = len(src)
# 初始化一个r,用于记录输入的src最后需要补几个0
# 才能补齐为三个字节
r = 0
# 进行字节分组,分为每三个字节一组
for offset in range(0, length, 3):
# 将所有可以组成3个字节的数据分组,避免分组中出现空
if offset + 3 <= length:
triple = src[offset:offset + 3]
else:
# 将剩下的不足3个字节的数据单独分组
triple = src[offset:]
# 记录需要给最后的字节补几个0x00
r = 3 - len(triple)
# 给最后的分组补充0x00
triple = triple + '\x00' * r
# 1、通过triple.encode()将字符转为字节(bytes)
# 2、通过大端模式(视系统而定, 为了保证数据的顺序不会反过来),将数据从内存中读出
# 3、将bytes数据转换为十进制的数值 int.from_bytes()
b = int.from_bytes(triple.encode(), 'big')
# 通过对二进制的数据进行移位操作,来将 3个字节 共24个bit 重组为 4个 6bit的数据
# 由于使用的是大端读取的数据,所以数据的顺序是从左至右
# 此处将数据 b(代表3个一组的字符数据的字节码的10进制数) 进行移位(操作其二进制数据),以重组数据bit
for i in range(18, -1, -6):
# 第一次移动,只保留最左侧的6位
if i == 18:
# index为移位重组为6bit之后,其二进制数据所对应的整数,此数即为Base64编码表中 字符的索引值
index = b >> i
else:
# 当后面移动时,每次移动6n位,再通过 & 为与运算符,保留最后的6位,其余均为补位0或 & 运算后的0
index = b >> i & 0x3F # 0x3F 为 0b0011 1111,用于仅保留移位后最右侧的6位二进制数
# alphabet为Base64对应表
res.append(alphabet[index])
# 此处通过上面记录的补了几个0x00,将其替换为 "=" 进行标识
for i in range(1, r+1):
# 0x3D为 = 的ascii码
res[-i] = 0x3D
return res
testStr = 'abcd'
print(myBase64(testStr))
使用python的base64包
import base64
testStr = 'abcd'
print(base64.b64encode(testStr.encode()))