16 - 文件操做-StringIO-BytesIO

1 文件操做

        讀寫文件是最多見的IO操做(通常說IO操做,指的是文件IO,若是是網絡,通常都會直接說網絡IO),在磁盤上讀寫文件的功能都是由操做系統提供的,操做系統不容許普通的程序直接操做磁盤(大部分程序都須要間接的經過操做系統來完成對硬件的操做),因此,讀寫文件就是請求操做系統打開一個文件對象(一般稱爲文件描述符),而後,經過操做系統提供的接口從這個文件對象中讀取數據(讀文件),或者把數據寫入這個文件對象(寫文件)。在操做系統中,文件經常使用的操做有:編程

功能 介紹
open 打開
read 讀取
write
close 關閉
readline 行讀取
readlines 多行讀取
seek 文件指針操做
tell 指針操做

1.1 open函數介紹

        在python中,咱們使用open函數來打開一個文件,而後返回一個文件對象(流對象)和文件描述符。打開文件失敗,則返回異常。windows

第三方模塊codecs包中提供的open函數,具有內置函數open的全部功能,而且在內部它對目標文件進行了編碼的與判斷處理,當你不知道目標文件的編碼類型時,使用codecs.open來講更可靠。緩存

1.2 打開操做

想要讀取文件,那麼就須要先行打開文件。下面來看一下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

1.2.1 mode模式

文件打開的方式有以下幾種:函數

描述字符 意義 備註
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]:

注意:

  • 咱們把這個 f 成爲文件句柄(用來標識一個文件的內存對象),包含文件名,字符集,大小,硬盤上的起始位置等等。
  • f 在這裏就變成了一個文件迭代器,咱們能夠經過使用for line in f ,去循環的讀取文件的每一行內容
# 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存儲的是對象,只有咱們讀取一行,它纔會把這行讀取到內存中,建議使用這種方法。

1.2.2 文件指針

        爲何文件只能讀一次,第二次讀就是空的了?是否能夠重複讀取呢?在對文件操做中,會存在一個指針用於指定當前光標的位置,當咱們讀完一次後, 文件指針就指向了文件的末尾了,那麼若是想要重複讀取,那麼只須要對指針的位置進行調整便可。

  1. mode = r , 指針起始在0
  2. mode = a , 指針其實在EOF(end of file:文件末尾)
f.tell()    # 顯示當前文件指針的位置
f.seek(offset, whence=0, /)   # 移動文件指針的位置
  • offset:偏移多少字節
  • whence:從哪裏開始

在文本模式下:

  • whence 0:缺省值,表示從頭開始,offset只能正整數
  • whence 1:表示從當前位置開始,offset只能設置爲0
  • whence 2:表示從EOF位置開始,offset只能設置爲0

    文本模式支持從開頭向後偏移的方式。whence爲1時,只支持偏移0(seek(0,0)),至關於原地不一樣,沒什麼卵用。whence爲2時,表示從EOF開始,但也只支持偏移0(seek(0,2)),至關於將文件指針移動到EOF。

二進制模式下:

  • whence 0: 缺省值,表示從頭開始,offset只能正整數
  • whence 1: 表示從當前位置,offset可正可負
  • whence 2: 表示從EOF開始,offset可正可負

    二進制模式支持任意起點的偏移,從頭、從尾部、從中間位置開始,向後seek能夠超界,可是向前seek的時候,不能超界,不然拋異常

1.2.3 緩衝區

        open函數的第三個參數爲buffering,叫做緩衝區,緩衝區是內存中的一小部分空間,用於存放準備寫入磁盤的數據,操做系統在進行磁盤寫入的時候,因爲磁盤速度要遠遠慢於CPU以及內存,若是每次寫入數據時,都要直接寫到磁盤上的話,那麼效率會很是的低,因此在操做系統層面會對寫入操做進行優化,好比緩衝區默認大小爲4K或者4K的倍數(爲何是4K,和磁盤扇區有關,這裏就不詳述了),當寫入的數據小於4K,會先存在緩衝區,當緩衝區滿了,或者超過必定時間沒有寫入,操做系統會將數據一次性的刷入(flush)磁盤。
        buffering的默認值爲-1,表示使用缺省大小的buffer,若是是二進制模式,由io.DEFAULT_BUFFER_SIZE決定,默認是4096或者8192,若是是文本模式,設備類型爲終端設備時,使用行緩存方式,若是不是,則使用二進制模式的策略。使用其餘值時,有以下含義:

  • 0 :只在二進制模式使用,表示關閉buffer
  • 1 :只在文本模式使用,表示使用行緩衝。(見到換行符就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的時候。

1.2.4 encoding編碼

        當咱們操做二進制文件的時候,數據都是十六進制的,並無什麼編碼的一說。而在使用文本模式時,就須要指定編碼類型了,經常使用的中文編碼好比,GBK、UTF-八、Unicode等,文件在內存中都是Unicode格式的,而存儲在磁盤上就是按照本地編碼的格式存儲了,windows上默認的是GBK(ANSI,cp936),當咱們存儲的文本數據不是默認的格式時,在打開時就會產生亂碼,這時可使用指定編碼格式打開文件。

encoding=None,默認值時,表示使用操做系統默認編碼類型
- windows: GBK(0xB0A1)
- Linux: UTF-8(0xE5 95 8A)

1.2.5 其餘參數

errors: 什麼樣的編碼錯誤將被捕獲。

  • None/strict:有編碼錯誤將拋出ValueError異常
  • ignore:表示忽略

newline: 文本模式中,換行的轉換。能夠爲None、''(空串)、'\r'、'\n'、'\r\n'。

  • 讀時,None表示'\r'、'\n'、'\r\n'都被轉換爲'\n';''(空串)表示不會自動轉換通用換行符;其餘合法字符表示換行符就是指定字符,就會按照指定字符分行
  • 寫時,None表示'\n'都會被替換爲系統指定缺省分隔符(os.linesep);'\n'表示'\n'不替換;其餘的合法字符表示'\n'會被替換爲指定的字符

closefd: 關閉文件描述符.

  • True: 表示關閉它
  • False: 會在文件關閉後保持這個文件描述符

    fileobj.fileno()查看

1.3 讀寫操做

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'

1.4 關閉操做

        當咱們打開一個文件時,那麼在系統中就會存在一個文件描述符對應這個文件,而且在文件系統中,一個程序可使用的文件描述符的數量是有限的,若是咱們使用完畢後不進行釋放,那麼頗有可能就耗光文件描述符的數量(Linux中默認的文件描述符爲1024,使用ulimit -a查看),產生異常,因此在使用完畢後,咱們就須要手動的進行關閉

f.close()

關閉文件的操做,會同時調用flush(),把緩衝區的數據刷入磁盤。當文件已經關閉,再次關閉沒有任何效果

1.5 上下文管理

        上面說咱們每次打開一個文件都須要手動的進行關閉,那麼若是忘記了,這個文件會一直佔用着對應的文件描述符,若是屢次打開一個文件不關閉,那麼每次都會佔用新的文件秒師傅,直到文件描述符用完。上下文管理就是爲了解決這種問題的,它有以下特色:

  1. 使用 with .. as 關鍵字。
  2. with語句塊執行完畢的時候,會自動關閉文件對象
  3. 不會開啓新的做用域(不會產生諸如函數的內部變量等)

        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

1.6 文件對象的其餘方法

文件對象的內置方法還有不少種,以下所示:

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:        文件名稱

2 StringIO模塊

        用於在內存空開闢一個文本模式的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的過程,能夠大大提升程序的運行效率.

3 BytesIO模塊

        用於在內存中開闢一個二進制模式的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來講,還有以下方法:

  1. readable(): 可讀嗎?
  2. writeable(): 可寫嗎?
  3. seekable(): 能夠調整指針嗎?

4 file-like對象

        類文件對象,能夠向文件對象一個操做,好比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個文件:

  • stdin: 標準輸入
  • stdout: 標準輸出
  • stderr: 標準錯誤輸出**
相關文章
相關標籤/搜索