IO
I/O指从输入设备读取数据到内存,经处理后,由内存中写入到输出设备的过程。
磁盘和网卡均为输入设备兼输出设备。
程序正常退出可自动关闭文件
编解码,所有的文件都是二进制文件,即字节流,但是二进制文件一些特殊的可以是文本文件,即字符流
缓冲IO与直接IO
缓冲I/O,Buffered I/O
- 缓冲IO基于缓冲区与IO设备进行数据传输,缓冲区是内存中的数据区域,通常与文件(磁盘数据的抽象)或套接字(网卡数据的抽象)等IO资源关联
- 在缓冲IO过程中,由操作系统将数据从输入设备写入输入缓冲区,应用程序从输入缓冲区读取数据到应用程序内存区域,由应用程序从应用程序内存区域将数据写入输出缓冲区,操作系统将输出缓冲区数据写入输出设备
- 缓冲IO优势在于可以减少对IO资源的访问次数从而提高性能
- 应用场景:缓冲IO为标准IO方式,大多数IO场景采用缓冲IO,典型案例为网络IO
直接I/O,Direct I/O
- 直接IO不经由缓冲区而直接与IO设备进行数据传输
- 在直接IO过程中,应用程序直接从输入设备读取数据到应用程序内存区域,直接从应用程序内存区域向输出设备写入数据
- 直接IO优势在于可以避免缓存减少内存消耗
- 应用场景:数据库
磁盘IO与网络IO
磁盘IO:以磁盘为IO设备,即从磁盘读取、向磁盘写入,通常采用缓冲IO,根据应用程序需求会选用直接IO。磁盘数据通常由文件系统抽象为文件,由此亦称文件IO。
网络IO:以网卡为IO设备,即从网卡读取、向网卡写入,均采用缓冲IO。网卡数据由TCP/IP四层生产模型层层抽象,其中网络接口层、网络层、传输层三层向上最终在传输层之上整体抽象为socket,应用层即应用程序所处分层,由此亦称Socket IO。
IO in Python
缓冲IO和直接IO的选用取决于应用程序的需求,一般情况使用缓冲IO,若需要避免数据缓存或需要进行低层磁盘操作则使用直接IO
Python中,以open为代表的文件操作为缓冲IO,基于mmap模块可以实现文件的直接IO,网络IO均为缓冲IO
磁盘IO模型
…
网络IO模型
网络IO采用缓冲IO方式,缓冲IO方式主要思路为:读取时,由应用进程发起读数据系统调用,内核线程先从从网卡读取数据到内核缓冲区,然后将数据从内核缓冲区复制到应用进程内存,写入与之相反。
显然网络IO过程中数据主要存在三个地方,网卡、操作系统内核缓冲区、应用程序内存,以读数据过程为例,第一阶段数据由网卡到内核缓冲区,第二阶段由内核缓冲区到网卡。两个阶段分别采用不同策略组合构成五种网络IO模型。
注意:不要先入为主地将物种IO模型名字中的阻塞非阻塞作为实现同步和异步的手段,而实际上,阻塞非阻塞与同步异步是两种不同范畴的内容,阻塞和非阻塞说的是应用程序从内核缓冲区获取数据这个阶段是否阻塞,而同步异步是说两个阶段是否阻塞
https://zhuanlan.zhihu.com/p/473639031
Blocking IO Model,阻塞IO模型
- 应用进程执行recvfrom函数发起读数据系统调用,并在数据获取完成之前阻塞
- 内核缓冲区未准备好数据就等待数据传输
- 内核缓冲区数据准备好后由内核线程将数据从内核缓冲区复制到应用程序
NonBlocking IO Model,非阻塞IO模型
- 应用进程执行recvfrom函数发起读数据系统调用,并直接返回数据是否准备好
- 内核缓冲区未准备好数据就等待数据传输,应用程序则反复调用recvfrom直至数据准备好
- 内核缓冲区数据准备好后由内核线程将数据从内核缓冲区复制到应用程序
IO Multiplexing Model,IO多路复用模型
- 应用进程执行select、poll或epoll监控多个socket文件描述符,在任意一个套接字数据准备好之前进程阻塞
- 内核缓冲区未准备好数据就等待数据传输
- 内核缓冲区数据准备好后返回可读信息
- 应用进程执行recvfrom函数发起读数据系统调用
- 由内核线程将数据从内核缓冲区复制到应用程序
IO多路复用方式:
- select,select采用轮询文件描述符的方式,有最大描述符限制,linux限制为1024,大多数平台支持select
- poll,poll采用轮询文件描述符的方式,没有文件描述符的最大限制
- epoll,epoll采用回调方式监控文件描述符,没有文件描述符最大限制,Level_triggered、Edge_triggered
- kqueue
Python中的select模块专注于I/O多路复用,提供了select poll epoll三个方法(其中后两个在Linux中可用,windows仅支持select),另外也提供了kqueue方法(freeBSD系统)。
Signal Driven IO Model,信号驱动IO模型
- 应用进程进行sigaction系统调用并返回
- 应用进程继续执行
- 内核等待数据准备好,准备好后发送SIGIO信号
- 应用进程执行recvfrom函数发起读数据系统调用
- 由内核线程将数据从内核缓冲区复制到应用程序
Asynchronous IO Model,异步IO模型
- 应用进程调用aio_read函数,告知内核线程socket描述符后返回
- 应用进程不阻塞
- 内核等待数据,数据准备好后将数据由内核空间复制到应用进程
- 第一阶段为从网卡到内核缓冲区,第二阶段为从内核缓冲区到应用程序
- 若当程序读取数据时无论是在第一阶段阻塞还是第二阶段阻塞亦或两个阶段都阻塞,都是在保证应用进程和内核线程间同步通信,故阻塞IO、非阻塞IO、IO多路复用、信号驱动IO均为同步IO,而只有异步IO实质上并未在应用进程和内核线程间同步通信
- 对于同步IO,在第二阶段应用进程均由于recvfrom系统调用而阻塞(等待内核复制数据),根据第一阶段不同实现方式分为阻塞IO、非阻塞IO、IO多路复用、信号驱动IO,阻塞IO即第一阶段应用进程阻塞,非阻塞IO即第一阶段不阻塞而采用轮询方式,IO多路复用第一阶段应用程序阻塞但监控多个socket,信号驱动IO采用异步方式
- 对于异步IO,第一阶段和第二阶段均为异步方式
- 阻塞IO和非阻塞IO名称中“阻塞”和“非阻塞”仅指第一阶段对内核线程等待数据期间应用进程采取的策略。
文件读写
open
open()为内置函数,用于读写文件
mode
- 读写
- x,create,创建,文件存在报错,可写不可读
- r,read,读入,默认读写方式,文件不存在报错,可读不可写
- w,write,写入,文件不存在会创建文件,存在清空文件内容,可写不可读
- a,append,追加,文件不存在会创建文件,光标移至文件末尾,可写不可读
- +,update,更新(扩展),在x、r、w、a基础上扩展功能为可读可写,不能单独使用
- 内容
- t,text,文本,默认读写内容,读取文本文件
- b,binary,二进制,读取二进制文件
- 使用
- 必须且只能指定一个读写模式(x/r/w/a四选一,扩展+可选)
- 必须且只能指定一个读写内容(t、b二选一,通常t可省略)
+不会因为可写而使得文件创建,r+模式打开文件,若文件不存在依然会报错
示例文本
file.txt12
你好9
世界9
。6
HELLO WORLD15
不念过往, 不畏将来30
读写文件
f = open('./file.txt', mode='r+', encoding='utf-8') # Unicode字符集,UTF-8编码方式
print(f.readable()) # True 是否可读
print(f.writable()) # True 是否可写
print(f.tell()) # 0 光标位置,按字节,最开始是0,0表示所有内容之前,不指向任何字节
print(f.read(11)) # file.txt12 按字符数量读取,默认读取所有,示例文本中第一行有12个字节
print(f.tell()) # 12 英文字母和标点占一个字节、换行(Windows CRLF)占两个字节
print(f.seek(15, 0)) # 15 移动光标到第15个字节 whence 0 :将开头作为参考位置 1 :将当前作为参考位置 2 :将末尾作为参考位置
print(f.tell()) # 15
print(f.readline()) # 好9 从当前位置向后读取一行,并不一定是完整一行 中文占3个字节
print(f.tell()) # 21
print(f.readlines()) # ['世界9\n', '。6\n', 'HELLO WORLD15\n', '不念过往, 不畏将来30'] 从当前位置向后读取若干行
print(f.tell()) # 79
print(f.seek(0)) # 0 回到最开始
print(f.write("不念过往, 不畏将来\n\n")) # 12 写入12个字符,写30个字节,写多少覆盖多少
print(f.tell()) # 30
print(f.truncate(12)) # 23 截断文件,返回截断后的文件字节数,在文件某个位置截断,若文件末尾在截断位置之后,截断位置之后所有内容清除,若文件末尾在截断位置之前,不足的部分补空格,默认在当前光标位置截断,size = f.tell()
print(f.tell()) # 30 截断操作不改变光标位置
print(f.truncate()) # 30 不足补充空格
print(f.tell()) # 30 光标位置不变
f.close() # 关闭文件
# 最终示例文本会变为"不念过往 "(后面是空格共18个)。
注:
- 在Python中
\n表示换行,但针对不同平台python的\n对应不同的字符。 - Windows
CRLF\r\n、UnixLF\n、MacOSCR\r。 - 把Windows的一个文本文件直接上传Linux,再打开会出现格式问题。
- Git作为分布式版本控制系统,同一份代码要在各个平台协作开发,其实现了代码源文件中换行字符的自动转换。
- 上述源码在WIndows下运行没问题,在Linux或MacOS运行需要根据上述规则修改光标移动参数。
用户交互
# 用户交互
id = input('请输入ID:')
print(id, type(id))
序列化
序列化是将内存中的对象转换成一定格式的二进制数据或文本数据,便于存储在文件中或者通过网络传输,后续根据该格式可从文件或者网络读入数据并重建对象的过程称之为反序列化。
- 把对象存储在内存之外
- 把对象传输到另一台机器的内存
json
https://www.json.org/json-en.html
# 序列化为json
import json
d = {1: "a", 2: "b"}
print(d, type(d))
d_dump = json.dumps(d)
print(d_dump, type(d_dump))
d_rd = json.loads(d_dump)
print(d_rd, type(d_rd))
d = {1: "键值对", 2: "b"}
f = open("./dump.txt", "w", encoding="utf-8")
json.dump(d, f, ensure_ascii=False)
f.close()
f = open("./dump.txt", encoding="utf-8")
d = json.load(f)
f.close()
print(d, type(d))
pickle
# 序列化为pickle定义的二进制格式
import pickle
d = {1: "a", 2: "b"}
print(d, type(d))
d_dump = pickle.dumps(d)
print(d_dump, type(d_dump))
d_rd = pickle.loads(d_dump)
print(d_rd, type(d_rd))
d = {1: "细节", 2: "b"}
f = open("./dump.txt", "wb")
pickle.dump(d, f)
f.close()
f = open("./dump.txt", "rb")
d = pickle.load(f)
f.close()
print(d, type(d))
shelve
# 序列化为shelve定义的二进制格式
import shelve
f = shelve.open('open_file')
f['key'] = {'int': 11, 'float': 1.2, 'string': 'ssd'}
f.close()
f = shelve.open('open_file', flag='r')
e = f['key']
f.close()
print(e)
ini文件操作
import configparser
config = configparser.ConfigParser()
config["DEFAULT"] = {
"ServerAliveInterval": 45,
"Compression": "yes"
}
config["bitbucket.org"] = {"user": "da"}
with open('config.ini', 'w') as configfile:
config.write(configfile)
config = configparser.ConfigParser()
config.read('config.ini')
print(config.sections())
print("bitbucket.org" in config)
print("b.org" in config)
print(config["bitbucket.org"]["user"])
print(config["DEFAULT"]["Compression"])
config.add_section("add-section")
config.remove_section("bitbucket.org")
config.set("add-section", "k1", "111")
config.set("add-section", "k2", "222")
toml
# 这是一个 TOML 文档
title = "TOML 示例"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
[database]
enabled = true
ports = [8000, 8001, 8002]
data = [["delta", "phi"], [3.14]]
temp_targets = { cpu = 79.5, case = 72.0 }
[servers]
[servers.alpha]
ip = "10.0.0.1"
role = "前端"
[servers.beta]
ip = "10.0.0.2"
role = "后端"
# 注释
# 最基本元素是键值对
# https://toml.io/cn/
#
# 键可以分为裸键和引号键,裸键仅由纯ASCII字符构成且不能为空,引号键由单引号或双引号以及Unicode字符构成且允许为空。
#
# 值可以分为基本类型和容器类型,基本类型有布尔值、整数、浮点数、字符串、日期时间,容器类型有数组、表(一般表和内联表)、表数组
#
# BNF
# https://www.jianshu.com/p/15efcb0c06c8
import tomllib
print(tomllib.load(open("demo.toml","rb")))
print(tomllib.loads("""
title = "TOML 示例"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
"""))
数据压缩
https://docs.python.org/zh-cn/3/library/zipfile.html
import zipfile
ret_zip_path = 'xxx_xxx_xxx_2023-09-08.zip'
with zipfile.ZipFile(ret_zip_path, mode="w", compression=zipfile.ZIP_DEFLATED) as ret_zip:
ret_zip.write('xxx_xxx_xxx_2023-09-08.xls', arcname='xxx_xxx_xxx_2023-09-08.xls')
import zipfile
f = zipfile.ZipFile("file.zip", 'r')
# 会返回压缩包内所有文件名的列表
print(f.namelist())
# 指定文件详细信息
print(f.getinfo("__pycache__/lua_script_for_redis.cpython-311.pyc"))
# 所有文件详细信息
print(f.infolist())
# 解压文件
for file in f.namelist():
f.extract(file, r'e:/work') # 在d:/Work中解压文件
f.close()
# w覆盖压缩,会删除其他文件,a追加压缩
f = zipfile.ZipFile("file.zip", 'a')
f.write("./zipfile_demo.py", "zipfile_domo.py", zipfile.ZIP_DEFLATED)
f.close()
# ZipFile.getinfo(name) 方法返回的是一个ZipInfo对象,表示zip文档中相应文件的信息。它支持如下属性:
#
# ZipInfo.filename: 获取文件名称。
# ZipInfo.date_time: 获取文件最后修改时间。返回一个包含6个元素的元组:(年, 月, 日, 时, 分, 秒)
# ZipInfo.compress_type: 压缩类型。
# ZipInfo.comment: 文档说明。
# ZipInfo.extr: 扩展项数据。
# ZipInfo.create_system: 获取创建该zip文档的系统。
# ZipInfo.create_version: 获取 创建zip文档的PKZIP版本。
# ZipInfo.extract_version: 获取 解压zip文档所需的PKZIP版本。
# ZipInfo.reserved: 预留字段,当前实现总是返回0。
# ZipInfo.flag_bits: zip标志位。
# ZipInfo.volume: 文件头的卷标。
# ZipInfo.internal_attr: 内部属性。
# ZipInfo.external_attr: 外部属性。
# ZipInfo.header_offset: 文件头偏移位。
# ZipInfo.CRC: 未压缩文件的CRC-32。
# ZipInfo.compress_size: 获取压缩后的大小。
# ZipInfo.file_size: 获取未压缩的文件大小。