字节交换#

字节顺序和ndarrays简介#

ndarray 是一个对象,它提供了一个在内存中数据的Python数组接口.

通常情况下,你希望用数组查看的内存的字节顺序与运行 Python 的计算机的字节顺序不同.

例如,我可能在一块使用小端序 CPU 的计算机上工作——比如 Intel Pentium,但我从一个大端序计算机编写的文件中加载了一些数据.假设我从一台 Sun(大端序)计算机编写的文件中加载了 4 个字节.我知道这 4 个字节代表两个 16 位整数.在大端序机器上,一个两字节整数先存储最高有效字节(MSB),然后存储最低有效字节(LSB).因此,这些字节在内存中的顺序是:

  1. MSB 整数 1

  2. LSB 整数 1

  3. MSB 整数 2

  4. LSB 整数 2

假设这两个整数实际上是1和770.因为770 = 256 * 3 + 2,内存中的4个字节将分别包含:0, 1, 3, 2.我从文件中加载的字节将包含这些内容:

>>> big_end_buffer = bytearray([0,1,3,2])
>>> big_end_buffer
bytearray(b'\x00\x01\x03\x02')

我们可能想使用一个 ndarray 来访问这些整数.在这种情况下,我们可以围绕这个内存创建一个数组,并告诉 numpy 这里有两个整数,它们是 16 位且为大端序:

>>> import numpy as np
>>> big_end_arr = np.ndarray(shape=(2,),dtype='>i2', buffer=big_end_buffer)
>>> big_end_arr[0]
np.int16(1)
>>> big_end_arr[1]
np.int16(770)

注意上面数组 dtype 中的 >i2. > 表示 ‘大端序’(< 是小端序),而 i2 表示 ‘有符号 2 字节整数’.例如,如果我们的数据表示一个无符号 4 字节小端序整数,则 dtype 字符串应为 <u4.

事实上,我们为什么不试试呢?

>>> little_end_u4 = np.ndarray(shape=(1,),dtype='<u4', buffer=big_end_buffer)
>>> little_end_u4[0] == 1 * 256**1 + 3 * 256**2 + 2 * 256**3
True

回到我们的 big_end_arr - 在这种情况下,我们的底层数据是大端序(数据端序),并且我们已经将 dtype 设置为匹配(dtype 也是大端序).然而,有时你需要翻转这些.

警告

标量不包含字节顺序信息,因此从数组中提取标量将返回本机字节顺序的整数.因此:

>>> big_end_arr[0].dtype.byteorder == little_end_u4[0].dtype.byteorder
True

NumPy 有意不尝试始终保持字节顺序,例如在 numpy.concatenate 中转换为本地字节顺序.

更改字节顺序#

正如你在介绍中所想象的那样,有两种方法可以影响数组字节顺序与其所查看的底层内存之间的关系:

  • 更改数组 dtype 中的字节顺序信息,以便它将底层数据解释为以不同的字节顺序.这是 arr.view(arr.dtype.newbyteorder()) 的作用.

  • 更改底层数据的字节顺序,保持 dtype 解释不变.这就是 arr.byteswap() 所做的.

在需要更改字节顺序的常见情况包括:

  1. 你的数据和数据类型字节序不匹配,你希望更改数据类型以使其与数据匹配.

  2. 你的数据和数据类型字节序不匹配,你希望交换数据以便它们与数据类型匹配.

  3. 你的数据和 dtype 的字节顺序匹配,但你希望数据交换并且 dtype 反映这一点

数据和数据类型的字节顺序不匹配,更改数据类型以匹配数据#

我们制造一些不匹配的东西:

>>> wrong_end_dtype_arr = np.ndarray(shape=(2,),dtype='<i2', buffer=big_end_buffer)
>>> wrong_end_dtype_arr[0]
np.int16(256)

解决这种情况的明显方法是更改 dtype,使其提供正确的字节顺序:

>>> fixed_end_dtype_arr = wrong_end_dtype_arr.view(np.dtype('<i2').newbyteorder())
>>> fixed_end_dtype_arr[0]
np.int16(1)

注意数组在内存中没有改变:

>>> fixed_end_dtype_arr.tobytes() == big_end_buffer
True

数据和类型字节序不匹配,更改数据以匹配 dtype#

如果你需要内存中的数据以特定顺序排列,你可能需要这样做.例如,你可能正在将内存写入需要特定字节顺序的文件.

>>> fixed_end_mem_arr = wrong_end_dtype_arr.byteswap()
>>> fixed_end_mem_arr[0]
np.int16(1)

现在数组在内存中 已经 发生了变化:

>>> fixed_end_mem_arr.tobytes() == big_end_buffer
False

数据和数据类型的字节顺序匹配,交换数据和数据类型#

你可能有一个正确指定的数组数据类型,但你需要数组在内存中的字节顺序相反,并且你希望数据类型匹配,以便数组值有意义.在这种情况下,你只需执行前两个操作:

>>> swapped_end_arr = big_end_arr.byteswap()
>>> swapped_end_arr = swapped_end_arr.view(swapped_end_arr.dtype.newbyteorder())
>>> swapped_end_arr[0]
np.int16(1)
>>> swapped_end_arr.tobytes() == big_end_buffer
False

将数据转换为特定数据类型和字节顺序的一个更简单的方法可以通过 ndarray 的 astype 方法实现:

>>> swapped_end_arr = big_end_arr.astype('<i2')
>>> swapped_end_arr[0]
np.int16(1)
>>> swapped_end_arr.tobytes() == big_end_buffer
False