在看这个示例的时候对于如何通过修改字节来改变原来数组里的值,产生了一些混淆,在此记录。
在研究这个示例前需要了解下 array 创建数组所用的类型码参数,以下列出了所能支持的类型码及其存储尺寸:
表格来自 array --- 高效的数值数组 — Python 3.9.6 文档
接下来看示例代码,第一步创建数组:
numbers = array.array('h', [-2, -1, 0, 1, 2])
这一步创建的数组所用的类型码为 h
即有符号短整型(unsigned short),以两个字节表示,而一个字节存储的是八位,也就是说这一步数组所存储的五个数,在内存里是这样的(左侧为低位,右侧为高位):
# -2
0111 1111 1111 1111
# -1
1111 1111 1111 1111
# 0
0000 0000 0000 0000
# 1
1000 0000 0000 0000
# 2
0100 0000 0000 0000
对于 unsigned short 类型,其最高位存储符号,1为负数,0为正数,为负数时,其值是反着算的,可以根据上面 -1 和 -2 的表示方式来很好理解,-1 作为最大的负数,以全 1 来表示。
当我们执行 memoryview
函数时,实际上直接拿到了这些数在内存里的存储内容,例如第三步:
memv_oct = memv.cast('B')
这一步将 memv 里的内容转换为 B
类型,即无符号字符,内存中的数据实际上没有变化,变化的是解读这些内容的方式。在 B
类型中,一个字节表示一个 int 类型的数,不带符号。
也就是说,原来由两个字节表示的 unsigned short 类型,将会被视作两个 unsigned char,也就是两个 python 中的 int 类型:
# -2 0111 1111 1111 1111 被拆分为 254 和 255
# 254
0111 1111
# 255
1111 1111
# -1 1111 1111 1111 1111 被拆分为两个 255
# 255
1111 1111
# 255
1111 1111
# 0 0000 0000 0000 0000 被拆分为两个 0
# 0
0000 0000
# 0
0000 0000
# 1 1000 0000 0000 0000 被拆分为 1 和 0
# 1
1000 0000
# 0
0000 0000
# 2 0100 0000 0000 0000 被拆分为 2 和 0
# 2
0100 0000
# 0
0000 0000
因此有了第四步的结果:
memv_oct.tolist()
# [254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
5 个数变成了 10 个数,但此时内存中的内容并没有变化,只是理解这些内容的方式变了而已。
而在第五步操作时,对内存进行了操作,即对此时的 memv_oct 的第 6 个数(索引从 0 开始)赋值为 4:
memv_otc[5] = 4
即第 6 个数 0 变成了 4,在内存中则为:
# 0 -> 4
0000 0000 -> 0010 0000
也就是原本表示第 3 个 unsigned short 类型的两个字节中,高位字节变成了 0010 0000
那么当回到 unsigned short 类型时,原本第三个是 0(0000 0000 0000 0000)变成了 1024(0000 0000 0010 0000)
也就是第六步的结果:
numbers
# array('h', [-2, -1, 1024, 1, 2])
这个例子里面没有直接对 numbers 进行操作,而是直接对 numbers 在内存中存储的字节进行了精确操作。
我的Github:Cheereus (FanToolMan) (github.com)