Python os.readv 函数
上次修改时间:2025 年 4 月 11 日
这份全面的指南探索了 Python 的 os.readv
函数,该函数执行从文件描述符到多个缓冲区的分散读取。我们将介绍它的 Unix 起源、性能优势和实际示例。
基本定义
os.readv
函数通过单个系统调用将数据从文件描述符读取到多个缓冲区中。 这被称为分散或向量化 I/O。
关键参数:fd (文件描述符), buffers (可写缓冲区的序列)。 返回读取的总字节数。 需要具有 readv() 系统调用的类 Unix 系统。
基本 readv 示例
此示例演示了 os.readv
最简单的用法,即从文件读取数据到两个单独的缓冲区中。 我们首先写入一些测试数据。
import os # Create test file with open("test.dat", "wb") as f: f.write(b"HelloWorldPythonReadv") # Open file and prepare buffers fd = os.open("test.dat", os.O_RDONLY) buf1 = bytearray(5) # First 5 bytes buf2 = bytearray(10) # Next 10 bytes # Perform scatter read buffers = [buf1, buf2] bytes_read = os.readv(fd, buffers) print(f"Total bytes read: {bytes_read}") print(f"Buffer 1: {buf1.decode()}") print(f"Buffer 2: {buf2.decode()}") os.close(fd)
此示例将数据写入文件,然后将其读回两个单独的缓冲区。 第一个缓冲区获取 5 个字节,第二个缓冲区获取 10 个字节。
os.readv
调用在一个操作中填充两个缓冲区,这比多个读取调用更有效。
读取到多个缓冲区
os.readv
可以读取到任意数量的缓冲区中。 此示例显示了从二进制文件读取到三个大小不同的缓冲区中。
import os # Generate binary data (24 bytes) data = bytes(range(24)) with open("data.bin", "wb") as f: f.write(data) fd = os.open("data.bin", os.O_RDONLY) # Three buffers of different sizes buf1 = bytearray(8) # First 8 bytes buf2 = bytearray(12) # Next 12 bytes buf3 = bytearray(4) # Final 4 bytes buffers = [buf1, buf2, buf3] bytes_read = os.readv(fd, buffers) print(f"Read {bytes_read} bytes total") print(f"Buffer 1: {list(buf1)}") print(f"Buffer 2: {list(buf2)}") print(f"Buffer 3: {list(buf3)}") os.close(fd)
这将创建一个包含 24 个字节 (0-23) 的二进制文件,然后将其读取到三个大小不同的缓冲区中。 输出显示每个缓冲区中的字节值。
读取的总字节数将是缓冲区大小的总和,除非到达 EOF。
使用 readv 进行部分读取
当文件小于总缓冲区容量时,os.readv
执行部分读取。 此示例演示了如何处理此类情况。
import os # Create small file (10 bytes) with open("small.dat", "wb") as f: f.write(b"ShortFile") fd = os.open("small.dat", os.O_RDONLY) # Buffers that total more than file size buf1 = bytearray(6) buf2 = bytearray(6) buf3 = bytearray(6) buffers = [buf1, buf2, buf3] bytes_read = os.readv(fd, buffers) print(f"Read {bytes_read} bytes (file has 10 bytes)") print(f"Buffer 1: {buf1.decode()}") print(f"Buffer 2: {buf2.decode()}") print(f"Buffer 3: {buf3.decode()}") # Will be empty os.close(fd)
此示例显示了当从一个小文件读取到更大的缓冲区中时会发生什么。 只有前几个缓冲区被填充,并且读取的总字节数与文件大小匹配。
第三个缓冲区保持为空,因为文件内容适合前两个缓冲区。
从不可查找的文件读取
os.readv
可以与管道和套接字等不可查找的文件一起使用。 此示例从管道读取到多个缓冲区中。
import os # Create pipe r, w = os.pipe() # Write data to pipe os.write(w, b"PipeDataForReadvExample") # Prepare buffers buf1 = bytearray(8) buf2 = bytearray(12) # Read from pipe buffers = [buf1, buf2] bytes_read = os.readv(r, buffers) print(f"Read {bytes_read} bytes from pipe") print(f"Buffer 1: {buf1.decode()}") print(f"Buffer 2: {buf2.decode()}") os.close(r) os.close(w)
这将创建一个管道,将数据写入其中,然后使用 os.readv
读取。 相同的方法适用于套接字和其他不可查找的文件描述符。
对于网络编程,readv 可以有效地将传入的数据收集到预分配的缓冲区中。
性能比较
此示例将 os.readv
与多个 os.read
调用进行比较,以演示向量化 I/O 的性能优势。
import os import time # Create large test file (1MB) data = os.urandom(1024 * 1024) with open("large.dat", "wb") as f: f.write(data) def test_readv(): fd = os.open("large.dat", os.O_RDONLY) buf1 = bytearray(512 * 1024) buf2 = bytearray(512 * 1024) os.readv(fd, [buf1, buf2]) os.close(fd) def test_multiple_read(): fd = os.open("large.dat", os.O_RDONLY) buf1 = bytearray(512 * 1024) buf2 = bytearray(512 * 1024) os.read(fd, buf1) os.read(fd, buf2) os.close(fd) # Time readv start = time.time() for _ in range(100): test_readv() readv_time = time.time() - start # Time multiple reads start = time.time() for _ in range(100): test_multiple_read() read_time = time.time() - start print(f"readv time: {readv_time:.3f}s") print(f"Multiple read time: {read_time:.3f}s") print(f"Ratio: {read_time/readv_time:.1f}x")
此基准测试创建一个 1MB 的文件,并使用两种方法读取 100 次。 由于系统调用较少,os.readv
通常表现出更好的性能。
确切的加速取决于系统和文件大小,但对于分散的读取,向量化 I/O 通常更有效。
使用 readv 进行错误处理
此示例演示了使用 os.readv
时的正确错误处理,包括处理部分读取和系统错误。
import os import errno try: # Try reading from invalid FD buf = bytearray(10) os.readv(999, [buf]) except OSError as e: if e.errno == errno.EBADF: print("Error: Bad file descriptor") else: print(f"OS error: {e}") # Handle partial read try: fd = os.open("empty.dat", os.O_CREAT | os.O_RDONLY) buf1 = bytearray(10) buf2 = bytearray(10) bytes_read = os.readv(fd, [buf1, buf2]) print(f"Read {bytes_read} bytes from empty file") os.close(fd) except OSError as e: print(f"Error: {e}") finally: if 'fd' in locals(): os.close(fd)
第一部分尝试从无效的文件描述符读取,显示了如何处理特定错误。 第二部分演示了从空文件读取。
在处理底层文件操作时,正确的错误处理至关重要,因为可能会出现很多问题。
高级:使用 readv 的 Memoryview
为了获得最大效率,os.readv
可以与 memoryview 对象一起使用,以避免不必要的复制。 此示例显示了优化的方法。
import os # Create test file data = b"MemoryViewExampleData" with open("memview.dat", "wb") as f: f.write(data) fd = os.open("memview.dat", os.O_RDONLY) # Allocate memory and create memoryviews buffer = bytearray(20) mv1 = memoryview(buffer)[0:10] # First 10 bytes mv2 = memoryview(buffer)[10:20] # Next 10 bytes # Read into memoryviews bytes_read = os.readv(fd, [mv1, mv2]) print(f"Read {bytes_read} bytes") print(f"Full buffer: {buffer.decode()}") print(f"View 1: {mv1.tobytes().decode()}") print(f"View 2: {mv2.tobytes().decode()}") os.close(fd)
此示例分配一个缓冲区,然后创建两个引用其部分的 memoryview 对象。 os.readv
直接写入这些视图。
使用 memoryview 可以避免额外的内存分配和复制,这对于高性能应用程序非常重要。
性能注意事项
- 更少的系统调用: readv 将多个读取合并为一个
- 缓冲区管理: 预分配减少开销
- 内存效率: Memoryview 阻止复制
- 对齐: 正确的缓冲区对齐提高了性能
- 内核绕过: 某些系统优化了向量化 I/O
最佳实践
- 缓冲区大小调整: 根据预期的数据调整缓冲区大小
- 错误处理: 始终检查返回值和错误
- 资源清理: 使用 try/finally 处理文件描述符
- 可移植性: 检查系统支持 (主要是 Unix)
- 基准测试: 测试您的用例的性能提升
资料来源
作者
列出所有 Python 教程。