大部分编程语言都没有直接给我们提供对于位进行操作的方法,有时候为了节约空间或者是实现一些特殊目的,我们需要对于一个存储空间中的某一段 “位串” 进行读写。
目录
我们要实现:对于第n字节进行写入数据和读取数据,同时其他3字节的数据不能受影响。
让我们利用Python来做这个实验,为方便表示,我们利用十六进制的表示方法来演示。
1个十六进制位相当于4个二进制位,所以1个字节的8位在十六进制的表示方法里占2个十六进制位,比如 二进制数 11110001 等于十六进制数 f1。
在Python中十六进制数据 f1 用 0xf1 表示。
向指定字节写入数据
当被写入的空间的二进制位全是0的时候
注意,被写入的那一字节空间在写入前必须是全0!
例如:我们有一个int类型的4字节数据
a = 0xab120056
让我们从二进制的角度来考虑,它的第二字节为全为0,我们要向它的第二字节写入数据 0x34
关键一步来了,我们要构造另一个int类型数据,其第二字节的数据是我们想要写入的数据 0x34,其他3字节都是0
我们把这个构造的新int数据叫做掩码
即
mask = 0x00003400
我们也可以用位的位移操作符 << 来构造这个掩码,只需要将数据位左移指定字节就好,比如我们要移动到第二字节就左移8位,第三字节就左移16位。
mask = 0x34 << 8
获得了包含有我们要写入的数据的掩码后,我们就可以进行写入操作了
我们只需要将被写入的数据和掩码做位或,将得到的值写回被写入的空间就行了
即
a = a|mask
至此,数据 0x34 已经被写入了int变量a所在的空间的第二字节,并且其他数据没有受到影响
print("a=“,a) # a=0xab123456
当被写入的空间的二进制位不是全是0的时候
如果被写入的那一字节在写入前不为全零,我们是不能按上边的方法直接构造掩码并写入的,我们要先将原来的非全零数据进行擦除,即先设法将其全置为零。
让我们对于数据a = 0xab123456的第三字节进行擦除
同样,我们再构造一个和被操作数据等长的掩码,将掩码的要擦除的字节位置全置为0,不希望擦出的字节位置全置为1。
我们要擦除第三字节,就将掩码的第三字节的二进制位全置为0,其余部分全置为1
mask = 0xff00ffff
让我们对于目标的第三字节进行擦除,只需用掩码与被操作的数据进行位与操作,将得到的值覆盖掉原来的值就好
a = a&mask
至此,第三字节的数据已经被擦除,即全置为0,并且其他数据没有受到影响
print("a=“,a) # a=0xab003456
擦除后,我们就可以像上边那样对于这一字节进行写入了,不再赘述。
从指定字节读取数据
我们已经成功地对于指定字节写入了数据,当我们想使用这些数据的时候,需要把它们读取出来。
假如我们有数据 a=0xab123456
我们想读取其第二字节的数据0x34
我们只需要构造一个这样的掩码,其第二字节二进制位全为1,其余位全为0
即
mask = 0x0000ff00
我们要读取目标数据的第二字节,只需要使用掩码与目标数据进行位与,再将得到的结果进行右位移,将想要的数据位移到最低位即可
即
a = a&mask
print("a=“,a) # a=0x00003400
a = a >> 8
print("a=“,a) # a=0x00000034
至此,我们就成功的从指定数据的第二字节读取了数据。
封装好的读写指定字节的python函数
def writeByte(var_to, data, which_byte):
# 构造含有数据的写入掩码
mask_data = data << (which_byte-1)*8
# 构造擦除指定字节的擦除掩码
mask_tozero = ~(0xff << (which_byte-1)*8)
# 不管要写入的位原来是什么情况,一律先擦除
var_to = var_to & mask_tozero
# 写入数据
var_to = var_to | mask_data
return var_to
def readByte(var_from, which_byte):
# 构造可以读取指定字节的读取掩码
mask_read = 0xff << (which_byte-1)*8
# 读取数据
data = var_from & mask_read
# 提取数据
data = data >> (which_byte-1)*8
return data
# 原始数据
a = 0xab123456
# 写入在第二字节写入数据 0xcd
a = writeByte(a, 0xcd, 2)
# 打印一下
print(hex(a)) # 0xab12cd56
# 读取第二字节的数据
data = readByte(a, 2)
# 打印一下
print(hex(data)) # 0xcd
位操作原理总结
规律
位操作真是一件很神奇的事情,明明就是一些简单的规则(与/或)
-
0 | 0 = 0
-
0 | 1 = 1
-
1 | 1 = 1
-
0 & 0 = 0
-
1 & 0 = 0
-
1 & 1 = 1
如果从另一个角度来看待这些操作,就会有新用法
-
用0 位或 任何二进制位对方不变
-
用0 位与 任何二进制位对方变成0
-
用1 位或 任何二进制位对方变成1
-
用1 位与 任何二进制位对方不变
将以上特性推广到二进制串
-
用全0位串 位或 任何二进制位串对方不变 (读取数据)
-
用全1位串 位与 任何二进制位串对方不变(读取数据)
-
用全1位串 位或 任何二进制位串对方变成全1(用1填充,和第1条配合用来将指定位置串用1填充而不影响其他位置)
-
用全0位串 位与 任何二进制位串对方变成全0(用0填充,和第2条配合用来将指定位置串用0填充而不影响其他位置)
-
用一个01数据串 位或 一个全0位串得到那个01数据串(用来写入数据)
-
用一个01数据串 位或 一个全1位串得到一个全1串(写入失败……(写入保护))
-
用一个01数据串 位与 一个全1位串得到那个01数据串(用来写入数据)
-
用一个01数据串 位与 一个全0位串得到一个全0串(写入失败……(写入保护))
写入数据的2种方法
使用 位或 向目标空间的第n位开始写入数据串
掩码的构造:构造一个和被写入空间等长的全0串,从第n位开始,将要写入的数据放入这个全0串
目标空间:要事先将被写入的位置串全部置0
操作:
-
目标空间数据 位或 掩码
-
得到的结果再写入目标空间
使用 位与 向目标空间的第n位开始写入数据串
掩码的构造:构造一个和被写入空间等长的全1串,从第n位开始,将要写入的数据放入这个全0串
目标空间:要事先将被写入的位置串全部置1
操作:
-
目标空间数据 位与 掩码
-
得到的结果再写入目标空间
读取数据的2种方法
使用 位与 来读取目标空间的第n位开始长度为L的数据串
掩码的构造:构造一个和被写入空间等长的全0串,从第n位开始到后边的L为都置1
操作:
-
目标空间数据 位与 掩码
-
得到的数据再 右位移 ,将数据段位移到最低位,得到想读取的数据。
使用 位或 来读取目标空间的第n位开始长度为L的数据串
掩码1的构造:构造一个和被写入空间等长的全1串,从第n位开始到后边的L为都置0
掩码2的构造:构造一个和被写入空间等长的全0串,从第n位开始到后边的L为都置1
操作:
-
目标空间数据 位或 掩码1
-
将上一步得到的结果 同 掩码2 进行 位与
-
得到的数据再 右位移 ,将数据段位移到最低位,得到想读取的数据。