内存映射数组

当需要存取一个很大的数据文件中的一小部分数据时,将整个文件读入内存进行操作显然很费资源的。使用内存映射数组(Memory-mapped-file array)能够帮助我们快速解决问题。

在32位操作系统中,程序所使用的所有内存映射数组的空间总和小于2GBytes。

内存映射数组使用memmap()创建,它的调用参数如下:

memmap(filename, dtype=uint8, mode="r+", offset=0, shape=None, order="C")
  • filename : 储存数组的文件名,除了”w+”模式之外,文件必须存在,并且已经储存有数据。
  • dtype : NumPy的数据类型,可以是内置的数据类型,也可以是用户自己定义的结构类型。
  • offset : 文件中储存数据的起始位置,以字节为单位。
  • mode : 文件操作模式,”r”表示只读,”c”表示可以修改数组但不写入文件,”r+”表示可对数组进行读写,并自动将结果保存回文件,”w+”表示创建文件或者覆盖已有的文件。
  • order : 元素排列格式,”C”表示C语言格式,”F”表示Fortran语言格式。

下面我们通过一个实例说明内存映射数组的用法。首先使用”w+”模式创建一个内存映射数组,运行了下面的语句之后,将会生成一个名为“tmp.dat”的文件,内容是10个值为0的字节。

>>> a = np.memmap("tmp.dat", mode="w+", shape=(2,5))
>>> a
memmap([[0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0]], dtype=uint8)
>>> file("tmp.dat").read()
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

接下来将a中的每个元素都改为字符’a’的ASCII码,然后调用flush()方法将所有改动写入文件:

>>> a[:] = ord("a")
>>> a.flush()
>>> file("tmp.dat").read()
'aaaaaaaaaa'

这时“tmp.dat”文件的内容变成了10个字符’a’。然后我们用”c”模式打开此文件,修改其中的一些元素为字符’b’,虽然内存中的数据已经修改,但是即使调用flush(),也不会将结果写入文件:

>>> b = np.memmap("tmp.dat", mode="c", shape=(2,5))
>>> b
memmap([[97, 97, 97, 97, 97],
       [97, 97, 97, 97, 97]], dtype=uint8)
>>> b[1] = ord("b")
>>> b
memmap([[97, 97, 97, 97, 97],
       [98, 98, 98, 98, 98]], dtype=uint8)
>>> b.flush()
>>> file("tmp.dat").read()
'aaaaaaaaaa'

“tmp.dat”文件内容仍然是10个’a’。如果使用”r+”模式,就会将改动写入文件。

>>> c = np.memmap("tmp.dat", shape=(2,5))
>>> c[1] = ord("c")
>>> c.flush()
>>> file("tmp.dat").read()
'aaaaaccccc'

此时我们再看看a和b中的值,数组a的值改变了,而数组b没有改变:

>>> a
memmap([[97, 97, 97, 97, 97],
       [99, 99, 99, 99, 99]], dtype=uint8)
>>> b
memmap([[97, 97, 97, 97, 97],
       [98, 98, 98, 98, 98]], dtype=uint8)

许多格式的文件(例如WAV和BMP)都有一个文件头,然后才是数据内容,为了正确读取数据,需要使用offset参数指定数据的偏移量,下面的例子演示了如何使用memmap读写BMP文件。读者可以使用Windows自带的画笔软件手工创建一张1000*1000的24bit的bmp文件,或者运行本书提供的程序创建:

02-numpy/create_bmp.py

创建一个大小为1000*1000的24bit的BMP文件

这里我们不关心文件头中有些什么内容,只想知道BMP文件头的大小。查看所创建的BMP文件的大小为3,000,054个字节,显然BMP文件头的大小为54个字节,其后是1000*1000*3个字节表示图像中每个像素的颜色。

然后我们使用下面的程序为图像中的每个像素赋值:

02-numpy/numpy_change_bmp.py

使用memmap数组修改BMP文件中的像素点的颜色

import numpy as np

# shape = 高,宽,颜色
bmp = np.memmap("tmp.bmp", offset=54, shape=(1000,1000,3)) #

# 产生一组从0到255的渐变值
tmp = np.linspace(0, 255, 1000).astype(np.uint8) #

# 水平蓝色渐变
bmp[:, :, 0] = tmp  #
# 垂直绿色渐变
bmp[:, :, 1] = tmp.reshape(-1,1)  #
# 红色成分
bmp[:, :, 2] = 127  #

bmp.flush() #

❶调用np.memmap()创建内存映射数组。offset参数指定了文件中数据的偏移量,而shape参数指定了数据的形状。BMP文件的高和宽为1000个像素,每个像素点为3个uint8的整数,因此得到的数组bmp是一个三维数组,其形状为(1000,1000,3)。第0轴表示BMP图像的高,第1轴表示宽,第2轴表示每个像素点的蓝绿红三种颜色的分量。

❷使用linspace()产生一组从0到255渐变的uint8数组,❸并将其分别赋值给bmp数组的蓝色和绿色部分,❹红色部分我们全部设置为127。❺最后调用flush()确保内存中的数据写回BMP文件中去。这样就得到了一张彩色渐变图像。

上一个主题

文件存取

下一个主题

SciPy-数值计算库

本页

loading...