文章篇幅较大,但又不可分割所以我尽量标题细化一下。
目录
流的分类
字节流
包裹流
io.TextIOWrapper
io.BufferedReader
io.BufferedWriter
io.BufferedRandom
io.BufferedRWPair
文本流
文件流
open方法
流的分类
### 分类
根据读写操作:输入流和输出流
根据是否遵循字符集规则:字节流和字符流
根据环境的不同:文件流、网络流、内存流、数据库流、控制台流等等
### 输入流和输出流
对于输入流和输出流这两个概念太熟悉了,但是也太概括性了,不便于我们很好的理解学习。
比如,我们常说的文件读写,此时我们使用一种介于磁盘和内存之间交互的流(文件流),那么此时的输入流和输出流就是一个相对概念了,如果参考内存,内存的输入用于读取、内存的输出用于写入,那么如果参考磁盘呢,磁盘的输入用于写入,磁盘的输出用于读取。
那么我们也可以直接将数据在存放在内存上进行读取(内存流),像redis这种基于内存存储的数据库研究的就很深哈,此时输入和输出都是参考内存的,输入流即用来写入,输出流即用来读取(这就没争议了哈)
### 字节流和字符流
字节流(Byte Stream):字节流用于处理二进制数据,即任意的数据,例如图像、音频、视频、压缩文件等。字节流以字节为单位进行读写,适用于那些没有明确字符编码的数据。
字符流(Character Stream):字符流用于处理文本数据,即由字符组成的数据,例如txt文件等。字符流以字符为单位进行读写,适用于那些由明确字符编码的数据。
字符流和字节流的区别在于处理的数据类型不同,字符流处理的是文本数据,需要遵循字符编码规则,而字节流处理的是二进制数据,不需要遵循字符编码规则。
在字符流中,字符编码规则用于将字符转换为字节序列,以便进行输入输出操作。例如,在UTF-8编码中,一个中文字符通常需要用3个字节来表示,而在ASCII编码中,一个英文字母只需要用1个字节来表示。因此,在进行文本输入输出时,需要根据具体的字符编码格式来进行处理。
在字节流中,不需要考虑字符编码规则,因为字节流处理的是二进制数据,可以包含任意的数据类型,例如图像、音频、视频等。在字节流中,数据以字节为单位进行读写,不考虑字符编码格式。
需要注意的是,在Python中,由于字符串类型是unicode编码的,因此在进行文本输入输出时,通常使用字符流进行处理,而在进行二进制数据输入输出时,通常使用字节流进行处理。在Python3中,io模块提供了io.TextIOWrapper和io.BufferedRWPair等类来实现字符流的输入输出操作,io.BytesIO类则用于实现字节流的输入输出操作。
字节流
### 字节流
(1) 以下为简要:
字节流作为流的基础,是我们首先需要学习的,在python中其实现类为io.ByteIO,其继承关系为:"io.ByteIO >> io_BufferedIOBase >> _IOBase >> object"。
使用前需要了解以下单指针和双指针。都是用来进行读写操作的,单指针毫无疑问读写将都使用这唯一的指针,读写都会使指针位置发生变化并且停留在读写后的位置,所以读写前一定要明确当前指针的位置。而双指针毫无疑问就是读和写分别使用一个指针来进行读和写操作,并且当读指针读取完毕以及写指针写完毕后都会回到初始位置也就是0位置(又称为指针归位)。
为什么要说指针问题呢?因为python中的字节流就是基于单指针进行读写的,所以在使用读写操作时需要注意指针位置。
在python中的字节流实现类中,允许我们可以在实例化时传入一个字节数据作为初始数据(不传也可以),且最多只有这一个参数,注意此时实例化传入的数据并不会改变指针的初始化位置(指针位置依然为0)
(2) 以下为实现类BytesIO的使用
注意:以下无论是偏移量还是长度量都是以字节为单位,并不是以字符为单位,所以可能出现读不完整在解码时候出现错误的情况。
(1) 导入:import io
(2) 创建:byteIO = io.ByteIO(b"Hello Joden")
(3) 当前指针位置写入:byteIO.write(byte_data),byte_data即字节数据,像纯ASCII字符可以直接使用b""方式来定义,而中文文本就必须老老实实使用encode了(b""语法糖形式只支持原生的ASCII字符,限制了中文字符使用,我想这么做的原因是更省内存,因为ASCII占用的空间比Unicode更小)。
(4) 当前指针位置读取:byteIO.read([size]),size即读取的长度量
(5) 指针定位:seek(offset,[whence]),offset即偏移量,whence即相对于哪个位置偏移(0/io.SEEK_SET: 默认值即内容开头位置,1/io.SEEK_CUR:当前指针位置,2/io.SEEK_END:内容末尾偏移),所以这个offset也可以是负值的(向前读取,倒序写入)
(6) 内容全部读取:getvalue向对比"seek+read"的方式更加简洁、高效,简洁就一句话搞定了,高效也很好理解指针读取需要在内存中做指针位置变量运算,而getvalue直接把示例对应的内存块数据全部返回不需要任何计算,所以更高效(推荐使用)
(7) 当前指针位置截取:truncate([size]),会从当前位置开始截取只保留前面部分截断掉后面部分,size即保留部分(前面部分)的长度量
(8) 当前指针位置:tell(),即返回当前指针的位置
(9) 读取单行:readline(),在当前指针位置开始读取一行。
(10) 读取多行:readlines(),在当前指针位置开始读取多行,返回list类型。
(11) 是否可读的:readable(),默认为True,可以通过继承方式重写该方法改变其默认行为
(12) 是否可写的:writeable(),默认为True,可以通过继承方式重写该方法改变其默认行为
(13) 是否可定位的:seekable(),默认为True,可以通过继承方式重写该方法改变其默认行为
(14) 上下文管理:close()、with语法,这两个用法是继承自IO基类IOBase的。流创建后并不会自动关闭,要么使用close()要么使用with语法去实现其的关闭,以免占用内存
(3) 代码示例:
import iobyteIO1 = io.BytesIO("你好,世界".encode('utf-8')) byteIO2 = io.BytesIO(b"Hello World") byteIO3 = io.BytesIO() byteIO3.write("Hello,世界".encode('utf-8'))print(byteIO1.read().decode('utf-8')) print(byteIO2.read().decode('ascii')) byteIO3.seek(0) print(byteIO3.read().decode('utf-8'))
包裹流
io.TextIOWrapper
### 以下为前言:
io.TextIOWrapper可以将其他流包裹起来,被包裹的流不用再考虑解编码问题(后面将学习的许多流都可以通过该是实现类包裹,从而得到优化),我们通常使用open()方法就是io.FileIO+io.TextIOWrapper+BufferedRandom的组合效果。
真正用来读取和写入的还是原来的流即被包裹的流,io.TextIOWrapper流并没有进行存储和读取操作。
### 以下为创建实例对象:
io.TextIOWrapper(stream,encoding)
stream:即一个流实例对象,将被包裹用来读写。
encoding:即指定该实例读写使用的字符集。
### 以下为相关方法:
注意:首先,使用TextIOWrapper只是简化了所包裹的字节流对象读写过程的编解码步骤,像指针、长度量依然是以的字节为单位(并不是以字符为单位);另外,io.TextIOWrapper实例对象并没有不参考指针位置就可以获取完整文本内容的方法,所以读写时要明确当前指针的位置,从而确保正确的读写文本内容(为了普用性,因为getvalue方法是基于内存的,比如包裹的是文件流的情况就不适用)。
buffer:获取实例中使用的字节流对象。
read():在字节流中从当前指针位置开始读取文本内容,解码参考实例的encoding参数(无则使用默认字符集)。
write():在字节流中从当前指针位置开始写入文本内容,编码参考实例的encoding参数(无则使用默认字符集)。
seek(offset,[whence]):指定指针位置,和前面学习的相同。
tell():获取查看当前指针位置。
flush():确保将数据写入实例中的字节流中。
readline():从当前指针位置读取一行文本。
readlines():从当前指针位置读取多行文本,以list类型返回。
truncate([size]):从当前位置开始截断,和前面学习的相同。
writeable():当前实例的字节流是否可写
readable():当前实例的字节流是否可读
(4) 以下为示例代码:import iob = io.BytesIO() wrapIO = io.TextIOWrapper(b, encoding='utf-8')wrapIO.write("你好,Joden") wrapIO.flush() # 确保内容写入缓存流b.getvalue().decode("utf-8") # 你好,Joden wrapIO.tell() # 14 wrapIO.seek(0) # 令指针回到开头 wrapIO.read() # 你好,Joden
io.BufferedReader
### 以下为前言:
像io.TextIOWrapper一样,用来修饰流的,顾名思义该流用于限制流只读,使该实例接口对被包裹的流只能读取内容,所以我也把它归类为包裹流,同样io.BufferedReader流并没有进行存储和读取操作(参考示例)。
注意,实例对象的指针偏移量、长度量依然是参考字节,以字节为单位(非以字符为单位)### 以下为实例化对象:
io.BufferedReader(stream,buffer_size)
方法参考io.TextIOWrapper接口(注意,为了普用性也没有实现getvalue方法)
### 以下为示例代码:import iofile = io.FileIO('./text.txt', 'r+') reader = io.BufferedReader(file)file.tell() # 0 reader.tell() # 0 reader.seek(0) reader.read().decode('utf-8') # 你好,Jodenreader.seek(0) file.truncate(0) # 把内容清空 print("###1: ", file.read()) # b""reader.seek(0) print("###2: ", reader.read()) # b""
io.BufferedWriter
### 以下为前言:
像io.BufferedReader一样,顾名思义该流用于限制流只写,使该实例接口对被包裹的流只能内容,也把它归类为包裹流,同样io.BufferedReader流并没有进行存储和读取操作(可以参考下面示例代码)。
注意,实例对象的指针偏移量、长度量依然是参考字节,以字节为单位(非以字符为单位)。
### 以下为实例化对象:
io.BufferedReader(stream,buffer_size)
方法参考io.TextIOWrapper接口(注意,为了普用性也没有实现getvalue方法)
### 以下为实例方法:
参考前面,不再说了
### 以下为实例代码:import iofile = io.FileIO('./text.txt', 'w+') # text.txt:无内容 reader = io.BufferedWriter(file)file.tell() # 0 reader.tell() # 0 reader.write("你好,Joden".encode('utf-8')) reader.flush()print(file.tell()) # 14 print(reader.tell()) # 14file.seek(0) file.truncate(0)print(file.tell()) # 0 print(reader.tell()) # 0
io.BufferedRandom
### 以下为前言:
先了解一下缓冲区的概念,是等需要磁盘文件数据再去读取磁盘数据快,还是提前将指定磁盘数据存储到内存块再等需要直接从内存读取快,显然是后者比较快(内存的读取速度比磁盘的读取速度快很多)。当使用write方法写入数据时缓冲区会先将数据暂存到内存,然后再按照一定规则写入到磁盘,当使用read方法读取数据时会先从缓冲区读取数据,不够时再从磁盘中获取。确实一次性的读取有没有缓存区对读取性能影响不大,缓存主要是针对文件读写频繁性操作进行优化的,意味着如果你读取的不频繁完全不需要缓存区。
而BufferedRandom就是缓存区的实现类,可以通过使用BufferedRandom来包裹文件流,来提高文件流的读写效率,当然也没限制不能包裹其他类型的流,但比如说包裹字节流(BytesIO)、文本流(StringIO)就没太大意义哈。
要注意的是BufferedRandom包裹的流,要求可读可写(二者缺一不可)。
另外,因为其没有限制被包裹的流,所以读写还是字节数据为基准,指针偏移量、长度量都以字节为基本单位。### 以下为创建实例:
io.BufferedRandom(stream,[buffer_size])
stream:即被包裹的流,将使用缓冲区策略。
buffer_size:可选参数,即缓冲区的大小,默认为8kb(以版本为准,参考io.DEFAULT_BUFFER_SIZE)
### 以下为相关方法:
参考BytesIO即可 (要注意的是实例并没有实现getvalue方法,原因前面说过了)### 以下为实例代码:
import iofile = io.FileIO('./text.txt', 'w+') # 可读写 buffer = io.BufferedRandom(file)buffer.write("你好,Joden".encode('utf-8'))buffer.seek(0) print(buffer.read().decode('utf-8')) # 你好,Joden
io.BufferedRWPair
### 以下为前言:
这个实现类,我用了一下,总是没达到预期效果,官网文档也给的简单(如果有大佬会使用欢迎指正,在此感激不尽)。
听说这个实现类是一个集成读写双流(io.BufferedReader和io.BufferedWriter)的实现类,也就是说将需要两个流作为参数,一个用于写,一个用于读,但是注意这两个流的指针是同步的(两个流中指针的位置始终保持一致),这个实现类通常用于流的转换(使用什么流写入,使用什么流读取)。
第一个参数为读取的流对象,第二个参数是写入的流对象。
因为无法达到预期的使用效果,后面就没了。。。
文本流
### 以下为前言:
实现类为io.StringIO,这是一个成熟的文本流实现类,直接实例化即可(不用再传入字节流对象),而且指针位置、长度量都将以字符为单位,而且编解码都使用utf-8,最后因为也是内存存储,所以io.StringIO实例中也是实现了getvalue方法的(推荐使用)。
### 以下为相关方法:
方法参考BytesIO即可。
### 代码示例:
import iostringIO = io.StringIO() stringIO.write("你好,Joden")stringIO.seek(3) print(stringIO.read()) # Joden print(stringIO.getvalue()) # 你好,Joden
文件流
### 以下为前言:
文件的创建的实现类,需要两个参数:文件名和文件模式(r,w,a,x,+)。
文件内容的写入和读取都是以字节内容为基准,所以下面方法提到的偏移量、长度量都是以字节为基本单位。
### 以下为实例化对象:
io.FileIO(file,mode):
file:即文件名包含文件路径
mode:即文件模式
### 以下为实例方法:
write(bytes):在当前指针位置开始写入字节数据(注意只有在可写模式下可用)。
read():在当前指针位置开始读取字节数据(注意只有在可读模式下可用)。
tell():获取查看当前指针位置。
seek(offset,[whence]):对指针在当前指针位置进行指定偏移量的移动。
close():关闭文件流,也可以使用with语法创建实例实现自动关闭。
其他方法类似于字节流,这里不再过多说明(注意,不同的是没有getvalue方法,因为这是存储在磁盘文件中的)。
### 以下为实例代码:import iofile = io.FileIO("./test1.txt", "w+")file.write("你好,Joden".encode('utf-8')) file.tell() # 14 file.seek(0) file.read().decode('utf-8') # 你好,Joden
open方法
### 前言
了解了这么多流,最常见的open方法肯定也是应用流的一个封装方法,现在来了解以下这个open方法,确保我们更正确地使用它。### open解析
如果mode = 'w'/'r',那么将是io.FileIO + io.TextIOWrapper的组合效果。
如果mode = 'a',那么将是io.FileIO + io.BufferedWriter。
如果mode = 'wb'/'rb',那么将是io.FileIO + io.BufferedWriter/io.BufferedReader的组合效果。
如果mode ='w+'/'r+'/'a+',那么将是io.FileIO + io.BufferedRandom + io.TextIOWrapper的组合效果。
如果mode = 'ab+'/'rb+'/'wb'+,那么将是io.FileIO + io.BufferedRandom的组合效果。
看到文末,你可知计算机知识的浩瀚?