Python文件读写、StringIO和BytesIO
1 IO的含義
在計算機中,IO是Input/Output的簡寫,也就是輸入和輸出。
由于程序和運行時數(shù)據(jù)是在內(nèi)存中駐留,由CPU這個超快的計算核心來執(zhí)行,涉及到數(shù)據(jù)交換的地方,通常是磁盤、網(wǎng)絡(luò)等,就需要IO接口。
比如你訪問百度首頁,瀏覽器就需要通過網(wǎng)絡(luò)IO獲取網(wǎng)頁。瀏覽器先會發(fā)送請求給百度服務(wù)器,告訴它想要的html網(wǎng)址,這個動作是往外發(fā)數(shù)據(jù),叫Output。接著百度服務(wù)器把網(wǎng)頁的內(nèi)容發(fā)送過來,這個動作是從外面接收數(shù)據(jù),叫Input。
所以,通常,程序完成IO操作會有Input和Output兩個數(shù)據(jù)流。當然也有只用一個的情況,比如,從磁盤讀取文件到內(nèi)存,就只有Input操作,反過來,把數(shù)據(jù)寫到磁盤文件里,就只是一個Output操作。
輸入輸出是相對的,要考慮具體的對象是什么。一般,當我們寫的程序需要讀取磁盤文件時,相當于把磁盤的數(shù)據(jù)輸入到程序中,對于程序來說,讀取的數(shù)據(jù)就屬于Input,但是對于磁盤來說,相當于把數(shù)據(jù)輸出給程序,輸出的數(shù)據(jù)屬于Output。
IO編程中,Stream(流)是一個很重要的概念,可以把流想象成一個水管,數(shù)據(jù)就是水管里的水,但是只能單向流動。Input Stream就是數(shù)據(jù)從外面(磁盤、網(wǎng)絡(luò))流進內(nèi)存,Output Stream就是數(shù)據(jù)從內(nèi)存流到外面去。對于瀏覽網(wǎng)頁來說,瀏覽器和服務(wù)器之間至少需要建立兩根水管,才可以既能發(fā)數(shù)據(jù),又能收數(shù)據(jù)。
由于CPU和內(nèi)存的速度遠遠高于外設(shè)的速度,所以,在IO編程中,就存在速度嚴重不匹配的問題。舉個例子來說,比如要把100M的數(shù)據(jù)寫入磁盤,CPU輸出100M的數(shù)據(jù)只需要0.01秒,可是磁盤要接收這100M數(shù)據(jù)可能需要10秒,怎么辦呢?有兩種辦法:
第一種是CPU等著,也就是程序暫停執(zhí)行后續(xù)代碼,等100M的數(shù)據(jù)在10秒后寫入磁盤,再接著往下執(zhí)行,這種模式稱為同步IO。
另一種方法是CPU不等待,只是告訴磁盤,“您老慢慢寫,不著急,我接著干別的事去了”,于是,后續(xù)代碼可以立刻接著執(zhí)行,這種模式稱為異步IO。
同步和異步的區(qū)別就在于是否等待IO執(zhí)行的結(jié)果。好比你去麥當勞點餐,你說“來個漢堡”,服務(wù)員告訴你,對不起,漢堡要現(xiàn)做,需要等5分鐘,于是你站在收銀臺前面等了5分鐘,拿到漢堡再去逛商場,這是同步IO。
你說“來個漢堡”,服務(wù)員告訴你,漢堡需要等5分鐘,你可以先去逛商場,等做好了,我們再通知你,這樣你可以立刻去干別的事情(逛商場),這是異步IO。
很明顯,使用異步IO來編寫程序性能會遠遠高于同步IO,但是異步IO的缺點是編程模型復(fù)雜。想想看,你得知道什么時候通知你“漢堡做好了”,而通知你的方法也各不相同。如果是服務(wù)員跑過來找到你,這是回調(diào)模式,如果服務(wù)員發(fā)短信通知你,你就得不停地檢查手機,這是輪詢模式。總之,異步IO的復(fù)雜度遠遠高于同步IO。
操作IO的能力都是由操作系統(tǒng)提供的,每一種編程語言都會把操作系統(tǒng)提供的低級C接口封裝起來方便使用,Python也不例外。
讀寫文件是最常見的IO操作。Python內(nèi)置了讀寫文件的函數(shù),用法和C是兼容的。
讀寫文件前,我們先必須了解一下,在磁盤上讀寫文件的功能都是由操作系統(tǒng)提供的,現(xiàn)代操作系統(tǒng)不允許普通的程序直接操作磁盤,所以,讀寫文件就是請求操作系統(tǒng)打開一個文件對象(通常稱為文件描述符),然后,通過操作系統(tǒng)提供的接口從這個文件對象中讀取數(shù)據(jù)(讀文件),或者把數(shù)據(jù)寫入這個文件對象(寫文件)。
2 讀文件
要以讀文件的模式打開一個文件對象,使用Python內(nèi)置的open()函數(shù),傳入文件名和標示符:
標示符’r’表示讀,這樣,我們就成功地打開了一個文件。
如果文件不存在,open()函數(shù)就會拋出一個IOError的錯誤,并且給出錯誤碼和詳細的信息告訴你文件不存在:
>>> f=open('C:\\Users\\hfqn\\Desktop\\notfound.txt', 'r') Traceback (most recent call last):File "<stdin>", line 1, in <module> FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\hfqn\\Desktop\\notfound.txt'如果文件打開成功,接下來,調(diào)用read()方法可以一次讀取文件的全部內(nèi)容,Python把內(nèi)容讀到內(nèi)存,用一個str對象表示:
>>> f.read() 'Hello, world!'最后一步是調(diào)用close()方法關(guān)閉文件。文件使用完畢后必須關(guān)閉,因為文件對象會占用操作系統(tǒng)的資源,并且操作系統(tǒng)同一時間能打開的文件數(shù)量也是有限的:
>>> f.close()由于文件讀寫時都有可能產(chǎn)生IOError,一旦出錯,后面的f.close()就不會調(diào)用。所以,為了保證無論是否出錯都能正確地關(guān)閉文件,我們可以使用try … finally來實現(xiàn):
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' try:f = open('\path\to\file', 'r')print(f.read()) finally:if f:f.close()但是每次都這么寫實在太繁瑣,所以,Python引入了with語句來自動幫我們調(diào)用close()方法:
with open('\path\to\file', 'r') as f:print(f.read())這和前面的try … finally是一樣的,但是代碼更佳簡潔,并且不必調(diào)用f.close()方法。
調(diào)用read()會一次性讀取文件的全部內(nèi)容,如果文件有10G,內(nèi)存就爆了,所以,為保險起見,可以反復(fù)調(diào)用read(size)方法,每次最多讀取size個字節(jié)的內(nèi)容。
另外,調(diào)用readline()可以每次讀取一行內(nèi)容,調(diào)用readlines()一次讀取所有內(nèi)容并按行返回list。因此,要根據(jù)需要決定怎么調(diào)用。
如果文件很小,read()一次性讀取最方便;如果不能確定文件大小,反復(fù)調(diào)用read(size)比較保險;如果是配置文件,調(diào)用readlines()最方便:
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' with open('\path\to\file', 'r') as f:for line in f.readlines():print(line.strip()) # 把末尾的'\n'刪掉前面講的默認都是讀取文本文件,并且是UTF-8編碼的文本文件。要讀取二進制文件,比如圖片、視頻等等,用’rb’模式打開文件即可:
>>> f = open('C:\\Users\\hfqn\\Desktop\\test.jpg', 'rb') >>> f.read() b''\x89PNG\r\n\x1a\n\x00\x00\x00...' # 十六進制表示的字節(jié)要讀取非UTF-8編碼的文本文件,需要給open()函數(shù)傳入encoding參數(shù),例如,讀取GBK編碼的文件:
>>> f = open('C:\\Users\\hfqn\\Desktop\\gbk.txt','r',encoding='gbk') >>> f.read() '人生苦短,我用python'遇到有些編碼不規(guī)范的文件,你可能會遇到UnicodeDecodeError,因為在文本文件中可能夾雜了一些非法編碼的字符。遇到這種情況,open()函數(shù)還接收一個errors參數(shù),表示如果遇到編碼錯誤后如何處理。最簡單的方式是直接忽略:
>>> f = open('C:\\Users\\hfqn\\Desktop\\gbk.txt','r',encoding='gbk', errors='ignore')3 寫文件
寫文件和讀文件是一樣的,唯一區(qū)別是調(diào)用open()函數(shù)時,傳入標識符’w’或者’wb’表示寫文本文件或?qū)懚M制文件:
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' >>> f = open('C:\\Users\\hfqn\\Desktop\\test.txt', 'w') >>> f.write('Hello, world!') >>> f.close()你可以反復(fù)調(diào)用write()來寫入文件,但是務(wù)必要調(diào)用f.close()來關(guān)閉文件。
當我們寫文件時,操作系統(tǒng)往往不會立刻把數(shù)據(jù)寫入磁盤,而是放到內(nèi)存緩存起來,空閑的時候再慢慢寫入。只有調(diào)用close()方法時,操作系統(tǒng)才保證把沒有寫入的數(shù)據(jù)全部寫入磁盤。忘記調(diào)用close()的后果是數(shù)據(jù)可能只寫了一部分到磁盤,剩下的丟失了。所以,還是用with語句來得保險:
with open('C:\\Users\\hfqn\\Desktop\\test.txt', 'w') as f:f.write('Hello, world!')要寫入特定編碼的文本文件,請給open()函數(shù)傳入encoding參數(shù),將字符串自動轉(zhuǎn)換成指定編碼。
4 StringIO和BytesIO
很多時候,數(shù)據(jù)讀寫不一定是文件,也可以在內(nèi)存中讀寫。StringIO就是在內(nèi)存中讀寫str。
要把str寫入StringIO,我們需要先創(chuàng)建一個StringIO,然后,像文件一樣寫入即可:
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' >>> from io import StringIO >>> f = StringIO() >>> f.write('hello') 5 >>> f.write(' ') 1 >>> f.write('world!') 6 >>> print(f.getvalue()) hello world!getvalue()方法用于獲得寫入后的str。
要讀取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!StringIO操作的只能是str,如果要操作二進制數(shù)據(jù),就需要使用BytesIO。
BytesIO實現(xiàn)了在內(nèi)存中讀寫bytes,我們創(chuàng)建一個BytesIO,然后寫入一些bytes:
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學(xué)習(xí)交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學(xué)習(xí)教程和PDF電子書! ''' >>> from io import BytesIO >>> f = BytesIO() >>> f.write('中文'.encode('utf-8')) 6 >>> print(f.getvalue()) b'\xe4\xb8\xad\xe6\x96\x87'注意,寫入的不是str,而是經(jīng)過UTF-8編碼的bytes。
和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是在內(nèi)存中操作str和bytes的方法,使得和讀寫文件具有一致的接口。
總結(jié)
以上是生活随笔為你收集整理的Python文件读写、StringIO和BytesIO的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python调用大漠插件
- 下一篇: python 超时重试方法