Python文件IO

參考文章:
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的方法,使得和讀寫文件具有一致的接口。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章