目錄python
讀寫文件是最多見的IO操做(通常說IO操做,指的是文件IO,若是是網絡,通常都會直接說網絡IO),在磁盤上讀寫文件的功能都是由操做系統提供的,操做系統不容許普通的程序直接操做磁盤(大部分程序都須要間接的經過操做系統來完成對硬件的操做),因此,讀寫文件就是請求操做系統打開一個文件對象(一般稱爲文件描述符),而後,經過操做系統提供的接口從這個文件對象中讀取數據(讀文件),或者把數據寫入這個文件對象(寫文件)。在操做系統中,文件經常使用的操做有:編程
功能 | 介紹 |
---|---|
open | 打開 |
read | 讀取 |
write | 寫 |
close | 關閉 |
readline | 行讀取 |
readlines | 多行讀取 |
seek | 文件指針操做 |
tell | 指針操做 |
在python中,咱們使用open函數來打開一個文件,而後返回一個文件對象(流對象)和文件描述符。打開文件失敗,則返回異常。windows
第三方模塊codecs包中提供的open函數,具有內置函數open的全部功能,而且在內部它對目標文件進行了編碼的與判斷處理,當你不知道目標文件的編碼類型時,使用codecs.open來講更可靠。緩存
想要讀取文件,那麼就須要先行打開文件。下面來看一下open函數的用法網絡
open(['file', "mode='r'", 'buffering=-1', 'encoding=None', 'errors=None', 'newline=None', 'closefd=True', 'opener=None'],)
file
:要打開的文件名(默認爲當前路徑,不然要指定絕對路徑)。mode
:表示打開文件的格式。buffering
: 設置緩衝區的大小(二進制模式和文本模式不一樣)encoding
:打開文件的編碼格式(文本模式)errors
: 轉碼錯誤時是否提示異常newline
: 文本模式中對新行的處理方式closefd
: 刪除描述符時,文件對象是否釋放app
Python 2.x中包含file和open兩個操做文件的函數,Python 3.x 中只有open,操做方法相同
f = file('/etc/passwd','r')
socket
文件打開的方式有以下幾種:函數
描述字符 | 意義 | 備註 |
---|---|---|
r | 只讀打開 | 文件的默認打開方式,文件不存在會報異常 |
w | 只寫打開 | 文件不存在會建立,存在的話,會覆蓋源文件(非追加) |
x | 建立並只寫一個新文件 | 文件已存在則報錯 |
a | 只追加寫模式 | 在文件末尾追加,文件不存在,新建並追加 |
b | 二進制模式 | 字節流 ,將文件就按照字節理解,與字符編碼無關。二進制模式操做時,字節操做使用bytes類型 |
t | 文本模式 | 字符流 ,將文件的字節按照某種字符編碼理解,按照字符操做,缺省模式 |
+ | 讀寫打開一個文件 | 給原來只讀、只寫方式打開提供缺失的讀或者寫能力 |
默認使用文本只讀模式打開,特殊文件須要用文本模式傳輸的話,建議使用b 優化
+符
,爲r、w、a、x提供缺失的讀或者寫的功能,可是,獲取文件對象依舊按照r、w、a、x本身的特徵。但+不能單獨使用,能夠認爲它是爲前面的模式字符作的加強功能。編碼
描述字符 | 意義 |
---|---|
r+ | 讀模式附加寫功能 |
w+ | 寫模式附加讀功能 |
a+ | 追加寫模式附加讀功能 |
x+ | 打開一個新文件可讀可寫 |
以上爲文本模式,二進制模式時只須要添加b便可.即r+b,或者rb+均可
In [2]: f = open('hello.txt','r') --------------------------------------------------------------------------- FileNotFoundError Traceback (most recent call last) <ipython-input-2-27a5c78b3969> in <module> ----> 1 f = open('hello.txt','r') FileNotFoundError: [Errno 2] No such file or directory: 'hello.txt' In [3]: f = open('123.txt') In [4]: f.read() Out[4]: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00abc' In [5]: f.close() In [6]: f = open('123.txt','w') In [7]: f.read() --------------------------------------------------------------------------- UnsupportedOperation Traceback (most recent call last) <ipython-input-7-571e9fb02258> in <module> ----> 1 f.read() UnsupportedOperation: not readable In [8]:
注意:
# for 循環遍歷文件,打印文件的每一行 In [11]: f = open('123.txt') In [12]: for i in f: ...: print(i,end='') ...: hello world my name is daxin thanks In [13]:
注意:這裏for line in fd,其實能夠從fd.readlines()中讀取,可是若是文件很大,那麼就會一次性讀取到內存中,很是佔內存,而這裏fd存儲的是對象,只有咱們讀取一行,它纔會把這行讀取到內存中,建議使用這種方法。
爲何文件只能讀一次,第二次讀就是空的了?是否能夠重複讀取呢?在對文件操做中,會存在一個指針用於指定當前光標的位置
,當咱們讀完一次後, 文件指針就指向了文件的末尾了,那麼若是想要重複讀取,那麼只須要對指針的位置進行調整便可。
f.tell() # 顯示當前文件指針的位置 f.seek(offset, whence=0, /) # 移動文件指針的位置
在文本模式下:
0
:缺省值,表示從頭開始,offset只能正整數1
:表示從當前位置開始,offset只能設置爲0whence 2
:表示從EOF位置開始,offset只能設置爲0
文本模式支持從開頭向後偏移的方式。whence爲1時,只支持偏移0(
seek(0,0)
),至關於原地不一樣,沒什麼卵用。whence爲2時,表示從EOF開始,但也只支持偏移0(seek(0,2)
),至關於將文件指針移動到EOF。
二進制模式下:
0
: 缺省值,表示從頭開始,offset只能正整數1
: 表示從當前位置,offset可正可負whence 2
: 表示從EOF開始,offset可正可負
二進制模式支持任意起點的偏移,從頭、從尾部、從中間位置開始,向後seek能夠超界,可是向前seek的時候,不能超界,不然拋異常
open函數的第三個參數爲buffering,叫做緩衝區,緩衝區是內存中的一小部分空間,用於存放準備寫入磁盤的數據,操做系統在進行磁盤寫入的時候,因爲磁盤速度要遠遠慢於CPU以及內存,若是每次寫入數據時,都要直接寫到磁盤上的話,那麼效率會很是的低,因此在操做系統層面會對寫入操做進行優化,好比緩衝區默認大小爲4K或者4K的倍數(爲何是4K,和磁盤扇區有關,這裏就不詳述了),當寫入的數據小於4K,會先存在緩衝區,當緩衝區滿了,或者超過必定時間沒有寫入,操做系統會將數據一次性的刷入(flush)磁盤。
buffering的默認值爲-1,表示使用缺省大小的buffer,若是是二進制模式,由io.DEFAULT_BUFFER_SIZE
決定,默認是4096
或者8192
,若是是文本模式,設備類型爲終端設備時,使用行緩存方式
,若是不是,則使用二進制模式的策略。使用其餘值時,有以下含義:
0
:只在二進制模式使用,表示關閉buffer1
:只在文本模式使用,表示使用行緩衝。(見到換行符就flush)大於1
:用於指定buffer的大小二進制場景:
In [3]: import io In [4]: f = open('123.txt','w+b') In [5]: print(io.DEFAULT_BUFFER_SIZE) 8192 In [6]: f.write("daxin".encode()) Out[6]: 5 In [7]: f.seek(0) Out[7]: 0 In [8]: f.read() Out[8]: b'daxin' In [9]: f.flush() # 默認狀況下緩衝區大小爲8192字節,因此咱們寫入的不會被當即刷入到磁盤上,若是咱們要手動刷入能夠手動執行flush命令 In [10]: f.close() # close會觸發flush操做 In [11]: f = open('123.txt','w+b',4) # 緩衝區爲4個字節 In [12]: f.write('dax'.encode()) # 3個字節不會刷入磁盤 Out[12]: 3 In [13]: f.write('in'.encode()) # 超過緩衝區大小會直接連同本次寫入的數據一塊兒刷入磁盤 Out[13]: 2 In [14]: f.seek(0) Out[14]: 0 In [15]: f.read() Out[15]: b'daxin' In [16]: f.flush() In [17]: f.close()
文本字符串場景:
In [18]: f = open('123.txt','w+') # 沒有指定緩衝區的大小,採用默認值-1(因爲不是終端設備,因此採用二進制的策略 由io.DEFAULT_BUFFER_SIZE控制 In [19]: !cat 123.txt In [20]: f.write('daxin') Out[20]: 5 In [21]: !cat 123.txt In [22]: f.write('\n') # 遇到換行符的時候並無刷入磁盤 Out[22]: 1 In [23]: !cat 123.txt In [29]: f = open('123.txt','w+',1) # 指定緩衝區的大小爲1(行) In [30]: f.write('daxin') Out[30]: 5 In [31]: f.write(' hello') Out[31]: 6 In [33]: !cat 123.txt # 沒有遇到換行符,因此不會刷入磁盤 In [34]: f.write('\n') # 遇到換行符,而後刷入磁盤 Out[34]: 1 In [35]: !cat 123.txt daxin hello In [36]:
buffering | 說明 |
---|---|
buffering=1 | 文本和二進制模式時,都是io.DEFAULT_BUFFER_SIZE控制 |
buffering=0 | 文本模式時不支持,二進制模式時表示關閉緩衝區 |
buffering=1 | 文本模式表示行換成,二進制模式表示一個字節 |
buffering>1 | 文本模式時,是io.DEFAULT_BUFFER_SIZE字節,flush後把當前字符串也寫入磁盤。二進制模式時,表示緩衝區的大小,直到超過這個值時,纔會刷入磁盤 |
通常來講,文本模式通常都用默認緩衝區大小,二進制模式能夠指定buffer的大小。除非咱們明確知道,不然選擇默認緩衝區大小是一個比較好的選擇,另外在平常編程中,明確知道須要寫磁盤了,都會手動調用一次flush,而不是等到自動flush或者close的時候。
當咱們操做二進制文件的時候,數據都是十六進制的,並無什麼編碼的一說。而在使用文本模式時,就須要指定編碼類型了,經常使用的中文編碼好比,GBK、UTF-八、Unicode等,文件在內存中都是Unicode格式的,而存儲在磁盤上就是按照本地編碼的格式存儲了,windows上默認的是GBK(ANSI,cp936),當咱們存儲的文本數據不是默認的格式時,在打開時就會產生亂碼,這時可使用指定編碼格式打開文件。
encoding=None,默認值時,表示使用操做系統默認編碼類型 - windows: GBK(0xB0A1) - Linux: UTF-8(0xE5 95 8A)
errors
: 什麼樣的編碼錯誤將被捕獲。
newline
: 文本模式中,換行的轉換。能夠爲None、''(空串)、'\r'、'\n'、'\r\n'。
closefd
: 關閉文件描述符.
False: 會在文件關閉後保持這個文件描述符
fileobj.fileno()查看
read(size=-1)
: 表示讀取的多少個字符或字節;負數或者None表示讀取到EOF(end of file),中文文本時,表示size個字符。
>>> f = open('123.txt','r+') >>> f.read(5) 'daxin' >>> f.read(5) ' hell' >>> f.read(1) 'o' >>> f.read(1) '\n' >>> f.read(1) '你'
readline(size=-1)
: 一行行讀取文件內容。size設置一次能讀取行內幾個字符或者字節。負數或者None表示讀取到EOF(end of file)
>>> f = open('123.txt','r+') >>> f.readline(3) 'a d' >>> f.readline() 'axin hello world\n' >>> f.readline(-10) 'c\n'
readlines(hint=-1)
: 讀取全部行並返回列表。當指定hint爲負數時,則會讀取全部行,當指定爲正數時,表示讀取hint個字符所在的行
。好比第一行有6個字符,第二行有3個字符,當咱們使用readlines(7)時,就會返回兩行,而若是使用readlines(5)時就會只返回第一行。
In [58]: f.readlines() Out[58]: ['1\n', '2\n', '3\n', '4\n', '5\n', '6\n', '7\n', '8\n', '9'] In [94]: f = open('123.txt','r+') In [95]: f.readlines(9) Out[95]: ['你好啊我了個去\n', '爲何\n'] In [98]: f.readlines(3) Out[98]: ['你好\n', '在'] In [99]:
write(s)
: 把字符串寫入到文件中並返回寫入字符串的個數。
writelines(lines)
: 將字符串列表寫入文件中。
In [60]: f = open('123.txt','r+') In [65]: f.seek(0,2) Out[65]: 25 In [67]: f.write('10') Out[67]: 2 In [68]: f.seek(25) Out[68]: 25 In [69]: f.read() Out[69]: '10' In [71]: f.writelines(['11','12']) In [72]: f.seek(25) Out[72]: 25 In [73]: f.read() Out[73]: '101112'
當咱們打開一個文件時,那麼在系統中就會存在一個文件描述符對應這個文件,而且在文件系統中,一個程序可使用的文件描述符的數量是有限的,若是咱們使用完畢後不進行釋放,那麼頗有可能就耗光文件描述符的數量(Linux中默認的文件描述符爲1024,使用ulimit -a查看),產生異常,因此在使用完畢後,咱們就須要手動的進行關閉
f.close()
關閉文件的操做,會同時調用flush(),把緩衝區的數據刷入磁盤。當文件已經關閉,再次關閉沒有任何效果
上面說咱們每次打開一個文件都須要手動的進行關閉,那麼若是忘記了,這個文件會一直佔用着對應的文件描述符,若是屢次打開一個文件不關閉,那麼每次都會佔用新的文件秒師傅,直到文件描述符用完。上下文管理就是爲了解決這種問題的,它有以下特色:
2.6以上版本支持with讀取,with open('/tmp/hello.txt') as fd:
,而後全部打開文件的操做都須要縮進,包含在with下才,這樣不須要明確的指定close,當新的代碼沒有縮進的時候,文件會自動關閉。
In [103]: with open('123.txt','r+') as f: ...: f.read() ...: In [105]: f.closed Out[105]: True
或者:
In [100]: f = open('123.txt','r+') In [101]: with f: ...: f.read() ...: In [102]: f.closed Out[102]: True
文件對象的內置方法還有不少種,以下所示:
fd.closed(): 判斷文件是否被關閉,若被打開提示False,沒有的話提示True fd.flush(): 把修改的內容,強制刷新到文件中去 fd.isatty: 判斷是不是一個終端文件 fd.mode: 查看文件的打開模式 fd.name: 查看文件的名稱 fd.next: 迭代的方法,和readline很像,區別是,next讀到末尾會報錯,readline會繼續返回空 fd.read: 一次性讀取全部內容,以字符串的方式存取 fd.readable(): 判斷文件是否可讀 fd.readlines: 一次性讀取全部內容,以列表的方式存取(適合操做小文件) fd.readline(): 每次讀取一行內容 fd.seek(0): 調整文件讀取的指針位置 fd.seekable(): 判斷文件是否能夠調整指針位置(tty,磁盤等文件是不能被seek的),能夠被seek則返回真,不然返回假 fd.tell(): 查詢文件目前讀取位置(以字符爲單位) fd.truncate(): 截取文件,從開頭開始截取,不指定指針位置的話,那麼會清空文件 fd.write: 把一個字符串寫入到文件中去 fd.writelines():把字符串列表寫入文件中 fd.xreadlines():讀一行打印一行,針對大文件很是適用 -----> Python 2.x 中適用,3.x中已經取消 fd.encoding: 查看文件的編碼 fd.writeable(): 判斷文件是否能夠寫 fd.fileno(): 返回文件在操做系統上的文件描述符(一個進程的運行默認會打開三個特殊的文件描述符:0表示 stdin、1表示 stdout,2表示stderr) fd.name: 文件名稱
用於在內存空開闢一個文本模式的buffer,能夠像文件對象同樣操做它,當close方法被調用時,這個buffer會被釋放.
使用前須要先進行導入, from io import StringIO
In [106]: from io import StringIO In [107]: s = StringIO() # 實例化一個StringIO對象用於讀寫,至關於 f = open('123.txt','r+') In [108]: s.write('123') Out[108]: 3 In [109]: s.readline() Out[109]: '' In [110]: s.seek(0) Out[110]: 0 In [111]: s.readline() Out[111]: '123'
getvalue()
: 獲取所有內容,根文件指針沒有關係.
In [106]: from io import StringIO In [107]: s = StringIO() In [108]: s.write('123') Out[108]: 3 In [109]: s.readline() Out[109]: '' In [110]: s.seek(0) Out[110]: 0 In [111]: s.readline() # 指針已經切換到文件末尾 Out[111]: '123' In [112]: s.getvalue() # 無視指針,能夠讀取全部 Out[112]: '123'
通常來講,磁盤的操做比內存的操做要慢得多,內存足夠的狀況下,通常的優化思路是少落地,減小磁盤IO的過程,能夠大大提升程序的運行效率.
用於在內存中開闢一個二進制模式的buffer,能夠像文件對象同樣操做它,當close方法被調用時,這個buffer會被釋放.
調用前須要先導入: from io import BytesIO
In [114]: from io import BytesIO In [115]: b = BytesIO() # 實例化一個BytesIO對象用於存儲二進制數據 In [116]: b.write(b'abc') Out[116]: 3 In [117]: b.seek(0) Out[117]: 0 In [118]: b.read() Out[118]: b'abc' In [119]: b.getvalue() # 一樣無視指針的存在 Out[119]: b'abc'
針對StringIO和BytesIO來講,還有以下方法:
類文件對象,能夠向文件對象一個操做,好比socket對象,輸入輸出對象(stdin,stdout)等都是類文件對象.
In [120]: from sys import stdin,stdout In [122]: stdout.write('123') 123 In [123]: type(stdout) Out[123]: colorama.ansitowin32.StreamWrapper In [124]:
每一個程序初始化運行時,都會打開3個文件: