4. 文件IO效率问题与解决技巧

一. 如何读写文本文件

实际案列

某文本文件编码格式(如UTF-8, GBK, BIG5), 
在python2和python3 中分别如何读取该文件?

原理

字符串的语义变化
python2            python3
--------------------------
str            ->  bytes 
unicode        ->  str   


- python2: 写入文件前对  unicode  编码, 读入文件后对 字节 进行解码

- python3: open函数指定't'的文本模式, encoding指定编码格式

读写代码

# python2  先encode编码,再写入,  读取后还要解码

s = u'我是python2'

type(s)
# 结果<type 'unicode>

f = open('a.txt', 'w')
f.write(s.encode('utf8'))
f.flush()       # 从内存中放入磁盘中   这里可以用f.close()


f = open('a.txt')
txt = f.read()   # '\xe6\x88.......   此时是字节
txt.decode('utf8')    # u'\xe6\x88.....   此时是unicode     可以print到 我是python2




# python3
s = '我是python3'
f = open('b.txt', 'wt', encoding='gbk')    # t可以不写  python3 默认文本模式打开
f.write(s)
f.flush()

f = open('b.txt', encoding='gbk')
txt = f.read()      # 直接读取
print(txt)      # 我是python3
print(type(txt))   # <class 'str'>  在python3 中即  unicode

二. 如何处理二进制文件

实际案例

wav  是一种音频文件的格式, 音频文件为二进制文件。
wav  文件由头部信息和音频采样数据构成。 前面为头部信息, 包括声道数, 采样频率, 编码位宽等等, 后面是音频采样数据


使用python, 分析一个 wav  文件头部信息, 处理音频数据。

解决方案

- open函数以二进制文件打开, 指定mode 参数为b
- 二进制数据可以用readinto, 读入到提前分配好的buffer中
- 解析二进制数据可以使用标准库中的struct模块的unpack方法

代码

import struct


def find_subchunk(f, chunk_name):
    f.seek(12)  # 从头 跳过12个字节   
    while True:
        name = f.read(4)  # 读取4个字节
        chunk_size, = struct.unpack('i', f.read(4))  # i 即int 解析4个字节     h 解析两个字节
        print(name)

        if name == chunk_name:
            return f.tell(), chunk_size  # f.tell当前文件的指针位置

        f.seek(chunk_size, 1)  # 1 表示当前    0 表示从头 为默认


f = open('demo.wav', 'rb')  # 二进制文件  带b

offset, size = find_subchunk(f, b'data')

import numpy as np

buf = np.zeros(size // 2, dtype=np.short)  # array([0,0,0,0,...0], dtype=int16)

f.readinto(buf)

buf //= 8  # 除8  获得整数结果   获取声音小的声音


f2 = open('out.wav', 'wb')
f.seek(0)
info = f.read(offset)
f2.write(info)

buf.tofile(f2)
f2.clode()

三. 如何设置文件的缓冲

实际案列

将文件内容写入到硬件设备时, 使用系统调用, 这类IO操作的时间很长。 为例减少IO
操作的次数, 文件通常使用缓冲区(有足够多的数据才进行系统调用)。 文件的缓冲行为,分为 
'全缓冲', '行缓冲', '无缓冲'

如何设置python 中文件对象的 缓冲行为?

全缓冲

  • 缓冲区满了 才将内容写入文件
# 全缓冲
f = open('a.bin', wb)
f.write(b'abc')   # 此时 打开a.bin文件  发现什么都没有, 因为现在数据 在缓冲区中
f.write(b'efg')   # 此时 缓冲区没有填满, 数据依然在缓冲区中

f.write(b'1'*(4096-6))  # 假设 磁盘的缓冲区大小为 4096   此时打开 a.bin  发现有内容了


# 文本模式下
f2 = open('a.txt', 'w')

f2.write('a'*4095)    # 此时a.txt没有数据
f2.write('bc')        # 超过4096 字节了   a.txt依然没有内容  为什么呢?

#  字节流wb 方式 打开 时, 数据   ->   B(4096字节缓冲区 encode和decode) -> Raw(无缓冲 可以直接写入文件)
#  文本模式  打开      , 数据   ->   TextIO(8192字节缓冲区) -> B    -> Raw

f2.write('2'*10000)  # 此时  a.txt 有内容了

指定缓冲区大小

f = open('a.bin', 'wb', buffering=8192)
f.write('........')

行缓冲

  • 遇到 换行符/n 就将缓冲区的内容写入 文件, 遇不到换行符 则和全缓冲一样。
  • linux 的shell终端 就是 行缓冲的
  • 只能在 文本模式中 使用
# 设置为行缓冲
f = open('a.bin', 'wb', buffering=1)
f.write('huanghuanchong')

无缓冲

# 无缓冲方法
#方法一
f.raw.write('aaaaaaaaa')

# 方法二  
f = open('a.bin', 'wb', buffering=0)
f.write('wuhuanchong')

四. 如何将文件映射到内存

实际案列


1. 在访问某些二进制文件时, 希望能把文件映射到内存中, 可以实现随机访问('framebuffer设备文件')

2. 某些嵌入式设备, 寄存器被编址到内存地址空间, 我们可以映射 '/dev/mem' 某范围, 去访问这些寄存器

3. 如果多个进程映射同一个文件, 还能实现进程通讯的目的

解决方案

 使用标准库 'mmap.mmap()函数', 将文件映射到 进程的内存地址空间

设置屏幕颜色

import mmap
# 屏幕设备文件
f = open('/dev/fb0', 'r+b')

size = 8294400
m = mmap.mmap(f.fileno(), size)   
# f.fileno 得到文件描述符    , os.open获取的文件描述符一样
# size如果为0  表示  文件的全部放入内存, 这里是特殊设备文件需要计算大小用0可能会出错
# m 有 如  m.write(b'abc)  类似文件的 一些操作

m[:size//2] = b'\xff\xff\xff\x00' * (size // 4 // 2) 
# 把一般的屏幕变为白色 和透明       一个字节是8位 \xff 正好一个字节   

m.close()
f.close()

# linux  下 ctrl +alt +F1 切换到终端模式, 运行该程序 屏幕一半才会变为白色

五. 如何访问文件的状态

实际案列

在某些项目中, 我们需要获得文件的状态, 列如:
    1. 文件的类型('普通文件、目录、符号链接、设备文件...')
    2. 文件的访问权限
    3. 文件的最后的访问/ 修改/ 节点 状态更改时间
    4. 普通文件的大小

解决方案

  1. 系统调用: 标准库 'os模块'中的系统调用 'stat' 获取文件状态

import os

# fd = os.open('b.py', os.O_RDONLY)  # 只读方式打开, 得到文件描述符   实际上就是一个数字 如23
# os.read(fd, 10)  # 文件描述符是给 系统调用的 方法用的, 读10个字节


s = os.stat('5_2.py')   # 返回状态结果对象   可以传入文件, 也可以传入 文件夹
s = os.lstat('5_2.py')   #  不会跟随 符号链接  比如a 是b 的符号链接(软链接)  不加l 参数a会自动变为b
'''
os.stat_result(st_mode=33261, st_ino=13242808, st_dev=16777220, st_nlink=1, st_uid=501, st_gid=20, 
              st_size=349, st_atime=1539776238, st_mtime=1531978808, st_ctime=1534324393)

st_mode   文件类型  和权限   如   lrwxrwxrwx
st_ino    文件系统inode 节点的索引   
st_dev    当前文件所在设备 当前磁盘的分区
st_nlink  硬链接数1 表示文件本身          shell命令  :ln a.txt b.txt 创建a的硬连接b, a的硬连接会由1变为2
st_uid    文件所有者id
st_gid    文件所有者所在组id
st_size   文件大小 字节
st_atime  最近访问时间
st_mtime  最近修改时间
st_ctime  创建时间
'''

# 目前的 状态结果对象  不容易观察, 需要与特定的掩码 做与操作, python给我们定义好了

# ----------------处理st_mode---------------------
import stat
stat.S_IFDIR & s.st_mode    # 0 不是目录
stat.S_IFCHR & s.st_mode    # 0 不是char设备
stat.S_IFREG & s.st_mode    # 32768得到数字   是普通文件

# 或者

stat.S_ISREG(s.st_mode)    # true 是普通文件
stat.S_ISLNK(s.st_mode)    # false 不是符号链接文件


stat.S_IRUSR & s.st_mode  # 236 是否用户刻度
stat.S_IXOTH & s.st_mode  #  是否用户可执行


# ----------------处理st_atime 等时间对象---------------------
import time
time.localtime(s.st_atime)




  1. 快捷函数: 标准库 'os.path' 下的一些函数, 使用起来更加简洁

os.path.isfile('5_3.py') # 是否是文件

os.path.isdir('5_3.py') # 是否是目录

os.path.getatime('5_3.py') # 获取访问时间

os.path.getsize('5_3.py') # 获取文件大小


#...........


六. 如何使用临时文件

实际案例

某项目中, 我们从传感器采集数据, 没收集到1G 数据后, 做数据分析, 最终只保存分析结果。
这样很大的临时数据如果常驻内存, 将消耗大量内存资源, 我们可以使用临时文件存储这些临时数据('外部存储')

临时文件不用命名, 且关闭后会自动被删除

解决方案

  • 使用标准库中的 TemporaryFile 以及 NamedTemporaryFile

  • 一般使用TemporaryFile即可

  • 当多进程 要访问同一个临时文件时, 可以使用命名的临时文件NamedTemporaryFile

from tempfile import TemporaryFile, NamedTemporaryFile

# 创建临时文件, 操作系统级别的  , 可以用os.open等命令完成
tf = TemporaryFile()
# 可以指定临时文件  放在哪个盘/目录     tf = TemporaryFile( dir='/tmp/')
tf.write(b'*' * 1024 * 1024)  # 写入磁盘
tf.write(b'*' * 1024 * 1024)
tf.write(b'*' * 1024 * 1024)
tf.write(b'*' * 1024 * 1024)
tf.seek(0)
tf.read(512)

# 删除临时文件
tf.close()


# 带名字的临时文件, 非操作系统级别的
ntf = NamedTemporaryFile()
ntf.name  # '/tmp/tempae7x06w7'

# 可以使用python库中的函数查看文件名和前缀
import tempfile
temfile.gettempdir()
tempfile.gettempprefix()
# 删除临时文件
ntf.close()   

# 在NamedTemporaryFile() 传入 delete=False, close时就不会自动删除

猜你喜欢

转载自blog.csdn.net/weixin_41207499/article/details/83118440