二进制;16进制; Byte , Python的bytes类; Base64数据编码; Bae64模块;

参考:中文维基

  • 二进制
  • 位操作(wiki)
  • Byte字节
  • 互联网数据处理:Base64数据编码
  • Python的模块Base64
  • 16进制简介
  • python: bytes对象
  • 字符集介绍:ascii

二进制简介:

In mathematics and digital electronics, a binary number is a number expressed in the base-2 numberal system or binary numeral system, which uses only two symbos: zero(0) and one(1)。

在数学和数字电路(集成电路),二进制数字是以2个数字为基础的系统(或二进制数字系统)表示的数字,它只使用2个符号0和1。

数字电子电路中,逻辑门的实现直接应用了二进制,因此现代的计算机和依赖计算机的设备里都用到二进制。

每个数字代表一个bit(Binary digit)

四则运算:

  • 加法:0+0=0,0+1=1,1+0=1,1+1=10
  • 减法:0-0=0,1-0=1,1-1=0,10-1=1
  • 乘法:0×0=0,0×1=0,1×0=0,1×1=1
  • 除法:0÷1=0,1÷1=1

计算机对二进制数据进行的运算(+、-、*、/)都是叫位运算,即将符号位共同参与运算的运算。

 

计算的效率:位操作(位运算)

因为计算机的数据存储只有0和1,为了效率,直接对二进制数字进行计算,无需先把二进制的数转换为十进制再进行加减乘除。

这就是汇编语言计算速度快于高级语言(比如C语言)的原因。当然代码的可阅读性就降低了。

➕法的例子,根据上面的四则运算规则,就可以通过二进制数进行计算了。

35:  0 0 1 0 0 0 1 1
47:  0 0 1 0 1 1 1 1
————————————————————
82:  0 1 0 1 0 0 1 0

乘法的例子

int a = 3;
int b = 2; #后面的b分别为4, 8
int c = a * b;

3:  0 0 0 0 0 0 1 1  *  2
————————————————————
6:  0 0 0 0 0 1 1 0

*********************************************

3:  0 0 0 0 0 0 1 1  *  4
————————————————————
12:  0 0 0 0 1 1 0 0

*********************************************

3:  0 0 0 0 0 0 1 1  *  8
————————————————————
24:  0 0 0 1 1 0 0 0

 通过上面的规律可知,a*b,

 如果b满足2^N的时候,就相当于把a的二进制数据向左边移动,移动的位数是N,这叫做移位运算,是位运算的一种。

 所以用代码可以这么表示,a << N。

 本例子是 3<<1,  3<<2, 3<<3。

 如果b不等于2^N,编译器会将b拆分。例子:

int a = 15;                 
int b = 13;      =>        int b = (4 + 8 + 1)
int c = a * b;            

运算转换为 15 * 4 + 15 * 8 + 15 * 1, 进行位运算,代码就是:

15 << 2 + 15 << 3 + 15 << 0

除法和乘法的原理相同,不过是改成右移动了。

参考:https://juejin.im/post/5a5886bef265da3e38496fd5

十进数转成二进数

例子:十进制数:59.25

59是整数部分,0.25是小数部分。分别使用不同的计算方式:

  • 整数部分:
    1. 进行带余除法运算。得到一个整数和一个余数。
    2. 对得到的整数继续进行第一步操作。直到步骤N得到商数为0。(商数:除法运算的结果。一般是指整数部分)
    3. 把上面多次带余除法运算得到的余数按照步骤从后往前排列组合。方法:(N代表第N步骤的余数):N组合N-1组合..1。组合得到就是二进制的整数数字部分。本例得到111011。
  • 小数部分:
    1. 对小数部分乘以2,得结果x,取x的整数部分。
    2. 对结果x乘以2,取其整数部分。如此反复,直到乘法得到的数字的小数部分全部为0为止。
    3. 读取所有乘法计算后得到的数字的整数部分,从第一步到最后一步组合。得到二进制的小数部分。本例是01.
整数部分:
59 ÷ 2 = 29 ... 1
29 ÷ 2 = 14 ... 1
14 ÷ 2 =  7 ... 0
 7 ÷ 2 =  3 ... 1
 3 ÷ 2 =  1 ... 1
 1 ÷ 2 =  0 ... 1
小数部分:
0.25×2=0.5
0.50×2=1.0

最后得到二进制数字59.25(10) = 111011.01(2)

二进位转成十进位

整数部分的转换方法:个位数乘以2的0次幂, 然后10位数乘以2的1次幂,如此每加一位那么2的次幂就加1,最后把得到数字相加就是十进制数字。

1001012 = [ ( 1 ) × 25 ] + [ ( 0 ) × 24 ] + [ ( 0 ) × 23 ] + [ ( 1 ) × 22 ] + [ ( 0 ) × 21 ] + [ ( 1 ) × 20 ]
1001012 = [ 1 × 32 ] + [ 0 × 16 ] + [ 0 × 8 ] + [ 1 × 4 ] + [ 0 × 2 ] + [ 1 × 1 ]
1001012 = 3710

非整数部分的转换方法:利用负次幂。例如0.01(2)

0 × 2−1 (0 × 12 = 0)
1 × 2−2 (1 × 14 = 0.25)

得到十进制数字:0.25


位操作(wiki)Bitwise operation

位操作程序设计中对位模式(bit patterns)或二进制数(binary numerals)的一元和二元操作。

在许多古老的微处理器上(simple low-cost processors),位运算比加减运算略快,比乘法运算要快很多, 在除法上明显的快。

现代处理器执行加法和乘法和位运算一样快,这是因为它们采用longer instruction pipelines and other architectural design choices, 但位操作降低资源的使用更节能。

位云算符:Bitwise operators

  • not  取反:      ~  取反是一元运算符,对一个二进制数的每一位执行逻辑操作。使数字1成为0,0成为1。
  • or    按位或      |    处理两个长度相同的二进制数,两个相应的二进位中只要有一个为1,该位的结果值为1。
  • xor   按位异或  ^   
    • 对等长二进制模式或二进制数的每一位执行逻辑异或操作。操作的结果是如果某位不同则该位为1,否则该位为0。
    • 2020-1-12再理解:按位置对2个数比较,有差异则为真(1),无差异则为假(0)
  • and  按位与     &  处理两个长度相同的二进制数,两个相应的二进位都为1,该位的结果值才为1,否则为0。

按位异或:

汇编语言的程序员们有时使用按位异或运算作为将寄存器的值设为0的捷径。用值的自身对其执行按位异或运算将得到0。并且在许多架构中,与直接加载0值并将它保存到寄存器相比,按位异或运算需要较少的*处理单元时钟周期。

    0101
XOR 0011
  = 0110

移位 Bit shifts

移位是一个二元运算符,用来将一个二进制数中的每一位全部都向一个方向移动指定位,溢出的部分将被舍弃,而空缺的部分填入一定的值。在类C语言中,左移使用两个小于符号"<<"表示,右移使用两个大于符号">>"表示。

算数移位 Arithmetic shift(具体见wiki)

  • ⬅️,补0
  • ➡️,复制。

逻辑移动 Logical shift

上一章讲二进制的乘法运算,就是逻辑移动。利用的是二进制数的特性。

应用逻辑移位时,移位后空缺的部分全部填0。

  • 位移就是补0。zeros are shifted in to replace discarded bits。左位移,逻辑移动和算数移动一样。
  • 但是右移动,逻辑移动是插入0bit,而算数位移是复制bit。
#乘法运算1*(2^3)
   0001(十进制1)
<<    3(左移3位)
 = 1000(十进制8)

# 除法运算10/(2^2), 移动第2次,得到2,去掉了小数部分。
   1010(十进制10)
>>    2(右移2位)
 = 0010(十进制2)

Circular shiift(具体见英文wiki)


Byte

字节(港澳台叫元组,Byte), 是数字信息的单位,最常见的是8个bits组成一个byte。

从历史上看,byte是bit的数量,bit用于在计算机上给文本字符编码。基于这个原因,在计算机学,byte是最小的内存地址单位。

  • Byte缩写B.
  • Bit缩写b。

1个8b的Byte,最大是11111111,转换为十进制是255。因此范围是0~255。

ASCII码:一个英文字母占一个Byte.即8个bit。


Base64(wiki)

Base64是一种基于64个可打印字符来表示二进制数据的表示方法。

可打印字符包括字母A-Za-z数字0-9,和2个特别字符+,/。合计64个。

每6个bit表示一个Base64字符。

Base64索引表:

数值 字符   数值 字符   数值 字符   数值 字符
0 A 16 Q 32 g 48 w
1 B 17 R 33 h 49 x
2 C 18 S 34 i 50 y
3 D 19 T 35 j 51 z
4 E 20 U 36 k 52 0
5 F 21 V 37 l 53 1
6 G 22 W 38 m 54 2
7 H 23 X 39 n 55 3
8 I 24 Y 40 o 56 4
9 J 25 Z 41 p 57 5
10 K 26 a 42 q 58 6
11 L 27 b 43 r 59 7
12 M 28 c 44 s 60 8
13 N 29 d 45 t 61 9
14 O 30 e 46 u 62 +
15 P 31 f 47 v 63 /

原理

63(2) = 111111(2)

所以只需6个bit就可以表示64个字符了。

例子:

编码"man"

文本 M a n
ASCII编码 77 97 110
二进制位 0 1 0 0 1 1 0 1 0 1 1 0 0 0 0 1 0 1 1 0 1 1 1 0
索引 19 22 5 46
Base64编码 T W F u

Man的ASCII编码对应十进制数字是77,97,110,转换为二进制数字见上图。共有24bit。

把上面的24个二进制数字分割成4部分,每部分6bit。比如第一部分的二进制码:010011。

4个部分转换为十进制是19,22,5,46。

使用Base64编码,查看编码表,对应的字符是TWFu。

本例子,Base64算法将3个字节的ASCII编码转换为了4个字符的编码。

相比较ASCII码,Base64更节省空间。ASCII3个Byte,可以存3个字母,但Base64用3个Byte可以存4个字母。

特殊的情况:编码"a"或“aa”这种不足3个字符的文本字符串怎么办?

答案:很简单,为不足的二进制位后补上0即可。

用途

Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据,包括MIME电子邮件XML的一些复杂数据。

(更节省空间)

在URL中的使用

因为Base64更省空间,所以用于网络传输数据上有优势。在HTTP环境下被经常使用。因为数据库对%符号占用的问题。出现了改进版的Base64。但原理都一样。


Python的base64模块

此模块提供了将二进制数据编码为可打印的 ASCII 字符以及将这些编码解码回二进制数据的函数。

它为 RFC 3548 指定的 Base16, Base32 和 Base64 编码以及已被广泛接受的 Ascii85 和 Base85 编码提供了编码和解码函数。

它支持所有 base-64 字母表 (普通的、URL 安全的和文件系统安全的)。

base64.b64encode(s)

对bytes-like objects进行编码,并返回编码后的bytes对象。

base64.urlsafe_b64encode(s)

使用的是URL和文件系统安全的字母表,用-和_代替标准Base64的+和/。

例子:

>>> base64.b64encode(b'xfbxefxff')
b'++//'
>>> base64.urlsafe_b64encode(b'xfbxefxff')
b'--__'

bytes对象

是由单个字节构成的不可变序列。

表示 bytes 字面值的语法与字符串字面值的大致相同,只是添加了一个 b 前缀.

例子:

一个能处理去掉=的base64解码函数:

import base64

def safe_base64_decode(s):
    length = len(s)
    r = length % 4
    if r == 0:
      return base64.b64decode(s)
    i = 1
    while i <= r:
      s = s + b"="
      i += 1
    return base64.b64decode(s)

# 测试:
assert b'abcd' == safe_base64_decode(b'YWJjZA=='), safe_base64_decode('YWJjZA==')
assert b'abcd' == safe_base64_decode(b'YWJjZA'), safe_base64_decode('YWJjZA')
print('ok')

⚠️备注:

去掉=后怎么解码呢?因为Base64是把3个字节变为4个字节,所以,Base64编码的长度永远是4的倍数,因此,需要加上=把Base64字符串的长度变为4的倍数,就可以正常解码了。

另外baes64的方法是对Byte类对象编码和解码,所以字符串前面带字符b。


16进制(wiki)

十六进制(简写为hex或下标16)在数学中是一种逢16进1的进位制

用数字0到9和字母A到F表示,其中:A~F相当于十进制的10~15,这些称作十六进制数字

在历史上,中国曾经在重量单位上使用过16进制,比如,规定16为一

现在的16进制则普遍应用在计算机领域,这是因为将4个Bit化成单独的16进制数字不太困难。1个字节(Byte)有8个bit,表示成2个连续的16进制数字。可是,这种混合表示法容易令人混淆,因此需要一些字首、字尾或下标来显示。

例:

F16的二进制表示为1111(2)

016的二进制表示为0000(2)

表示方法

不同电脑系统编程语言对于16进制数值有不同的表示方式:

C语言、C++、ShellPythonJava语言及其他相近的语言使用字首“0x”,例如“0x5A3”。

最常用(或常见)表示十六进制数值的方式是将 '0x' 加在数字前,或在数字后加上小字 16。例如 0x2BAD 和 2BAD16 都是表示十进制的11181(或1118110), 用二进制表示则需要2个Bytes(16个bit): 0010101110101101。

十六进制的转换

十进制转十六进制,采用余数定理分解。类似十进制转换为2进制。除数改为16.

例如将487710转成十六进制:

4877÷16=304....13(D)

304÷16=19....0

19÷16=1....3

1÷16=0....1

这样就计到487710=130D16

编程中的函式

Python:

int('ff', 16) #255,把ff16转换为十进制数字255
hex(255) #0xff, 转换为16进制,用前标记符号0x表示

Python:  bytes对象

是由单个字节构成的不可变序列。

bytes 函数返回一个新的 bytes 对象,该对象是一个 0 <= x < 256 区间内的整数不可变序列。它是 bytearray 的不可变版本。

bytes 对象只负责以字节(二进制格式)序列来记录数据。

如果采用合适的字符集,字符串可以转换成字节串;反过来,字节串也可以恢复成对应的字符串

由于 bytes 保存的就是原始的字节(二进制格式)数据,因此 bytes 对象可用于在网络上传输数据,也可用于存储各种二进制格式的文件,比如图片、音乐等文件。

转化方法

1. 表示 bytes 字面值的语法与字符串字面值的大致相同,通过添加了一个 b 前缀,构建字节串。

2.class bytes([source[, encoding]])

参数

  • source: 为整数,返回一个长度为source的初始化bytes对象。
  • 如果 source 为字符串,则按照指定的 encoding 将字符串转换为字节序列 
  • 如果 source 为可迭代类型,则元素必须为[0 ,255] 中的整数;否则会报告❌ValueError: byte must be in range(0, 256)
  • 如果没有输入任何参数,默认就是初始化数组为0个元素。

bytes 字面值中只允许 ASCII 字符(无论源代码声明的编码为何)。

创建bytes对象的2种方式:

  • b"xxx",用字面量的方式创建
  • bytes()函数创建。例子:bytes(range(10)),创建由整数组成的可迭代对象。

如果使用字符串,需要指定编码方式:

b = bytes('h',encoding='ascii')  #利用内置bytes方法,将字符串转换为指定编码的bytes
>>> bytes("h",encoding='ascii')
b'h'
>>> bytes.decode(b'x68')  #把b'x68'解码得到ascii对应的符号
'h'

3.

调用字符串的encode()函数来转化成bytes对象的字节串。

bytes.decode()可以反向转化成字符串。

>>> b'x68'.decode()
'h'
>>> "h".encode()
b'h'

例子

1.使用bytes()函数,例子:

 十进制99的16进制是0x63,对应的ascii码是小写字母c:

 64(10) = 0x40(16)  , 对应的ascii码是@

>>> a = bytes([64,99])
>>> a
b'@c'

2.

>>> b1 = bytes()
>>> b2 = b''
>>> b3 = b'hello'
>>> b3
b'hello'
>>> b3[0]
104
>>> b3[2:4]
b'll'
>>> b4 = bytes('你好,Python!', encoding='utf-16')
>>> b4
b'xffxfe`O}Yx0cxffPx00yx00tx00hx00ox00nx00!x00'

备注:ascii字符集表

计算机底层并不能保存字符,但程序总是需要保存各种字符的,那该怎么办呢?

计算机“科学家”就想了一个办法:为每个字符编号,当程序要保存字符时,实际上保存的是该字符的编号;当程序读取字符时,读取的其实也是编号,接下来要去查“编号一字符对应表”(简称码表)才能得到实际的字符。

所谓的字符集,就是所有字符的编号组成的总和。早期美国人给英文字符、数字、标点符号等字符进行了编号,他们认为所有字符加起来顶多 100 多个,只要 1 字节(8 位,支持 256 个字符编号)即可为所有字符编号一一这就是 ASCII 字符集

后来各个国家都为本国文字进行编码,为了解决兼容问题,使用了2个字节(16位,65536个字符编码)的字符集--Unicode字符集。实际使用的 UTF-8, UTF-16 等其实都属于 Unicode 字符集。