+-
关于python的IO读写的二三事

IO读写内情

关于io读写,可以分为缓存io与直接io两种。而前者缓存io是当前最为常用的一种io机制:即使用缓冲区buffer(内存中的一块地址),来防止对硬件的频繁访问,由此减少读写操作的时间消耗和硬件本身的消耗。

缓存io:
基于操作系统将硬件与用户程序分隔的思想,实现上会在内核空间创建一个buffer,在读操作时,操作系统会把数据从硬件读入内核空间的buffer,用户进程再从内核空间的buffer复制到自己的用户空间(应用程序地址空间)中,之后若是再次读取会去检查内核空间中的buffer区,减少与硬件的io次数。同样对于写操作,也是先把用户空间中的内容写到内核空间中,应用程序不用等待,系统自动把内核空间buffer中的内容写入磁盘。

tips:
具体来说,在用户空间中,也是会指定一段内存空间来存放从内核buffer读取来的数据,我们也可以称之为用户空间的buffer区。这样用户应用程序每次读写一定数据,不需要每次都直接与内核空间进行通信,可以先寄存在用户空间的buffer中一段时间,可以减少用户空间与内核空间之间的通信次数,即减少系统调用次数,一定程度上减少开销。(不过一般未明显指定,buffer都是指的内核空间的buffer

advantage:

缓存io使用了操作系统内核缓冲区,在一定程度上分离了应用程序空间和实际的物理设备,更安全。 缓存io可以减少读盘的次数,从而提高性能。

disadvantage:

内核空间和用户空间之间频繁的数据拷贝也会对CPU以及内存造成较大的开销。

直接io简介:
直接io就是取消内核空间中的buffer缓冲区,数据直接从硬件传输到用户空间。这样可以减少内核缓冲区到用户程序缓存的数据复制操作的开销。

较典型的应用场景如数据库管理系统这类应用,它们更倾向于选择它们自己的缓存机制,因为数据库管理系统往往比操作系统更了解数据库中存放的数据,数据库管理系统可以提供一种更加有效的缓存机制来提高数据库中数据的存取性能。

直接io的缺点:如果访问的数据不在应用程序缓存中,那么每次数据都会直接从磁盘加载,这种直接加载会非常缓存。通常直接io与异步io结合使用,会得到比较好的性能。(异步io:当访问数据的线程发出请求之后,线程会接着去处理其他事,而不是阻塞等待)

以上只是简单地介绍了一下io机制,实际上的io机制是更复杂的,可以参考以下文章对其深入了解一下:
缓存io,直接io,内存映射
linux中直接io机制介绍
io读写原理

python io读写

The io module provides Python’s main facilities for dealing with various types of I/O. There are three main types of I/O: text I/O, binary I/O and raw I/O. These are generic categories, and various backing stores can be used for each of them.

A concrete object belonging to any of these categories is called a file object, Other common terms are stream or file-like object.

Independent of its category, each concrete stream object will also have various capabilities: it can be read-only, write-only, or read-write.

All streams are careful about the type of data you give to them. For example giving a str object to the write method of a binary stream will raise a TypeError. So will giving a bytes object to the write() method of a text stream.

对于file object, or stream, or file-like object来说,在io module中对其定义了一些通用的接口,如:read,readline,readlines,write,close,seek,tell,flush等。

open函数

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

对于这里的buffering参数,所指定的就是内核空间中的buffer大小,可以参考:python file buffer io.open是该builtin function的一个alias,open函数返回的就是上文所提到的file object, or stream, or file-like object。所以对于open所返回的对象,它也享有那些通用的接口。 对应前文所述的缓存io机制,可以更深层次理解open函数背后做了什么。

io.StringIO

io.StringIO(initial_value='')

A text stream using an in-memory text buffer. It inherits TextIOBase.

The text buffer is discarded when the close() method is called.

:该类是用于text读写的,即它不支持bytes类型数据的读写,对于bytes类型的数据可以使用io.BytesIO(initial_bytes=None);另外一点因为没有与硬件进行交互的需求,所以可以推测该buffer区域是在用户空间内的,并不是在内核空间。

getvalue()
Return a str containing the entire contents of the buffer.

:该方法不会改变stream position的位置,于此相对的是通用的接口read,使用后stream position会像一个指针一样顺着一个个字符的读取流到buffer的末位。

通用接口

上文的read,readline,readlines,write和close都不用介绍了,在平常对于file object, or stream, or file-like object的操作中,这些都是很常用的方法。以下简单介绍一下:seek,tell,flush这三个method.

seek(offset,whence=SEEK_SET)
Change the stream position to the given byte offset from whence position.

whence value:

SEEK_SET or 0 – start of the stream (the default); offset should be zero or positive SEEK_CUR or 1 – current stream position; offset may be negative SEEK_END or 2 – end of the stream; offset is usually negative

tell()
return the current stream position.

tips: with read、seek、tell, you can play with the stream position.

flush()
Flush the write buffers of the stream if applicable.This does nothing for read-only and non-blocking streams-如之前的StringIO使用该方法就是无效的。

:该方法是把buffer中的内容flush进file object相应的文件(硬件设备)中,但是不会影响stream position的位置。平时我们常用的close其实不只是关闭buffer,它也是先flush,然后再关闭buffer。

对于缓存这个概念的理解:
把数据从not-easy-access的地方移动到easy-access的位置,减少重复读取所需的开销。
大于所需的读入数据到一个中间性的区域,之后再需要额外的数据可以直接从中间性区域拿取,而不用再次从数据源读入。