Python文件IO

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014157109/article/details/91483275

参考文章:
https://www.cnblogs.com/ymjyqsx/p/6554817.html
https://blog.csdn.net/sxingming/article/details/52164249
https://blog.csdn.net/lucyxu107/article/details/82728266
更详细信息见python标准库:
https://docs.python.org/zh-cn/3.7/library/index.html

1 IO的含义

1.1 IO

在计算机中,IO是Input/Output的简写,也就是输入和输出。

由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。

比如你访问百度首页,浏览器就需要通过网络IO获取网页。浏览器先会发送请求给百度服务器,告诉它想要的html网址,这个动作是往外发数据,叫Output。接着百度服务器把网页的内容发送过来,这个动作是从外面接收数据,叫Input。

所以,通常,程序完成IO操作会有Input和Output两个数据流。当然也有只用一个的情况,比如,程序从磁盘读取文件到内存,就只有Input操作,反过来,程序把数据写到磁盘文件里,就只是一个Output操作。

IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。对于浏览网页来说,浏览器和服务器之间至少需要建立两根水管,才可以既能发数据,又能收数据。

1.2 同步IO,异步IO

由于CPU和内存的速度远远高于外设的速度,所以,在IO编程中,就存在速度严重不匹配的问题。举个例子来说,比如要把100M的数据写入磁盘,CPU输出100M的数据只需要0.01秒,可是磁盘要接收这100M数据可能需要10秒,怎么办呢?有两种办法:

  • 第一种是CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO

  • 另一种方法是CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步IO

同步和异步的区别就在于是否等待IO执行的结果。好比去买汉堡,服务员告诉没了,要现做,需要等,于是你是站在收银台前面等拿到汉堡再去逛商场,还是先去逛商场,等服务员通知再去拿呢?

很明显,使用异步IO来编写程序性能会远远高于同步IO(因为相同的时间里能做更多的事情),缺点是编程模型复杂----你得知道什么时候通知你“汉堡做好了”,而通知你的方法也各不相同。如果是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。总之,异步IO的复杂度远远高于同步IO。

操作IO的能力都是由操作系统提供的,读写文件是最常见的IO操作,操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。

2 文件读写

文件的读写是通过open(),read()函数来实现的。

2.1 open()函数的用法

open(file, mode=‘r’, buffering=-1, encoding=None)

open() 函数支持的文件打开模式(第二个参数):

模式 意义
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式
w 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除如果该文件不存在,创建新文件
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入
+ 打开一个文件进行更新(可读可写)。
b 二进制模式。

在这里插入图片描述

  • 不管是 w 还是 w+ 模式,当使用这两种模式打开指定文件时,open() 函数都会立即清空文件内容,实际上都无法读取文件内容。
  • 程序使用 r 或 r+ 模式打开文件,则要求被打开的文件本身是存在的,使用 r 或 r+ 模式都不能创建文件。
  • 使用 w、w+、a、a+ 模式打开文件,则该文件可以是不存在的,open() 函数会自动创建新文件。
  • b 模式可被迫加到其他模式上,用于代表以二进制的方式来读写文件内容(计算机中的文件来说,文本文件只有很少的一部分,大部分文件其实都是二进制文件,包括图片文件、音频文件、视频文件等。)

关于open()函数的第三个参数[, buffering]:

  • 第三个参数是 0(或 False),那么该函数打开的文件就是不带缓冲的;
  • 第三个参数是 1(或 True),则该函数打开的文件就是带缓冲的,此时程序执行 I/O 将具有更好的性能。
  • 第三个参数是大于 1 的整数,则该整数用于指定缓冲区的大小(单位是字节);
  • 第三个参数为任何负数,则代表使用默认的缓冲区大小。

计算机外设(比如硬盘、网络)的 I/O 速度远远低于访问内存的速度,而程序执行 I/O 时要么将内存中的数据写入外设,要么将外设中的数据读取到内存,如果不使用缓冲,就必须等外设输入或输出一个字节后,内存中的程序才能输出或输入一个字节,这意味着内存中的程序大部分时间都处于等待状态。

因此,一般建议打开缓冲。在打开缓冲之后,当程序执行输出时,程序会先将数据输出到缓冲区中,而不用等待外设同步输出,当程序把所有数据都输出到缓冲区中之后,程序就可以去干其他事情了,留着缓冲区慢慢同步到外设即可;反过来,当程序执行输入时,程序会先等外设将数据读入缓冲区中,而不用等待外设同步输入。

关于第四个参数encoding:

当使用 open() 函数打开文本文件时,程序使用的是哪种字符集呢?总是使用当前操作系统的字符集,比如 Windows 平台,open() 函数总是使用 GBK 字符集。因此,上面程序读取的 test.txt 也必须使用 GBK 字符集保存;否则,程序就会出现 UnicodeDecodeError 错误。

如果要读取的文件所使用的字符集和当前操作系统的字符集不匹配,则有两种解决方式:

  • 使用二进制模式读取,然后用 bytes 的 decode() 方法恢复成字符串。
  • 利用 codecs 模块的 open() 函数来打开文件,该函数在打开文件时允许指定字符集。
# 指定使用二进制方式读取文件内容
f = open("read_test3.py", 'rb', True)
# 直接读取全部文件,并调用bytes的decode将字节内容恢复成字符串
print(f.read().decode('utf-8'))
f.close()


上面程序在调用 open() 函数时,传入了 rb 模式,这表明采用二进制模式读取文件,此时文件对象的 read() 方法返回的是 bytes 对象,程序可调用 bytes 对象的 decode() 方法将它恢复成字符串。由于此时读取的 read_test3.py 文件是以 UTF-8 的格式保存的,因此程序需要使用 decode() 方法恢复字符串时显式指定使用 UTF-8 字符集。

import codecs
#指定使用utf-8 字符集读取文件内容
f = codecs.open("read_test4.py", 'r', 'utf-8', buffering=True)
while True:
    #每次读取一个字符
    ch = f.read(1)
    #如果没有读取到数据,则跳出循环
    if not ch : break
    #输出ch
    print (ch, end='')
f.close()

上面程序在调用 open() 函数时显式指定使用 UTF-8 字符集,这样程序在读取文件内容时就完全没有问题了。

2.2 read()函数的使用

read()函数的作用:按字节或字符读取文件内容。
如果使用了 b 模式,则每次读取一个字节;如果没有使用 b 模式,则每次读取一个字符。

f = open("read_test.py", 'r', True)
while True:
    # 每次读取一个字符
    ch = f.read(1)
    # 如果没有读到数据,跳出循环
    if not ch: break
    # 输出ch
    print(ch, end='')
f.close()

read()的参数:可传入一个整数作为参数,用于指定最多读取多少个字节或宇符。如果在调用 read() 方法时不传入参数,该方法默认会读取全部文件内容到内存,文件较大时会出现问题。
调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。因此,要根据需要决定怎么调用。

如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便:

with open('\path\to\file', 'r') as f:
    for line in f.readlines():
        print(line.strip())         # 把末尾的'\n'删掉

2.3close()函数及with open(…) as…:

文件使用完毕后必须调用close()函数来关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。
由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try … finally来实现:

try:
    f = open('\path\to\file', 'r')
    print(f.read())
finally:
    if f:
        f.close()

但是每次都这么写实在太繁琐,所以,Python引入了with语句来自动调用close()方法

with open('\path\to\file', 'r') as f:
    print(f.read())

3 StringIO和BytesIO

StringIO作用就是在内存中读写str。

要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:

>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())#getvalue()方法用于获得写入后的str。
hello world!

要读取StringIO,可以用一个str初始化StringIO,然后,像读文件一样读取:

>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
...     s = f.readline()
...     if s == '':
...         break
...     print(s.strip())
 
Hello!
Hi!
Goodbye!

BytesIO作用是在内存中读写bytes,我们创建一个BytesIO,然后写入一些bytes:

>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

和StringIO类似,可以用一个bytes初始化BytesIO,然后,像读文件一样读取:

>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

StringIO和BytesIO是在内存中操作str和bytes的方法,使得和读写文件具有一致的接口。

猜你喜欢

转载自blog.csdn.net/u014157109/article/details/91483275
今日推荐