目录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个文件: