一、文件操作
A.介绍
在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。
我们用python或其他语言编写的应用程序若想要把数据永久保存下来,必须要保存于硬盘中,这就涉及到应用程序要操作硬件,众所周知,应用程序是无法直接操作硬件的,这就用到了操作系统。操作系统把复杂的硬件操作封装成简单的接口给用户/应用程序使用,其中文件就是操作系统提供给应用程序来操作硬盘虚拟概念,用户或应用程序通过操作文件,可以将自己的数据永久保存下来。
#1. 打开文件,得到文件句柄并赋值给一个变量 #2. 通过句柄对文件进行操作 #3. 关闭文件什么是句柄?
1.代码的运行是在内存中运行的,在下一步要读文件内容,而文件内容在硬盘上,所以需要将硬盘上的文件加载到内存中,open()函数则提供了这个功能,
相当于向操作系统要了一个鱼网,这个鱼网就是句柄。open实际上是在调操作系统,由操作系统最终给你返回一个鱼网,即句柄将句柄赋值给一个变量f,
有了这个f句柄,就可以操作硬盘的文件了。
2.有了鱼网(f),下一步想要什么鱼,就用鱼网捞就行了,或者想往操作系统里放什么鱼,都可以通过鱼网进行。对应的代码就是read,write等方法,如:f.read()
3.关闭文件.实际上关闭的不是文件,而是将鱼网回收。如果不关闭,那操作系统就一直给你发放鱼网,就会占用操作系统的资源一直不释放。
B.举例
#1.1、由应用程序向操作系统发起系统调用open(...) #1.2、操作系统打开该文件,并返回一个文件句柄给应用程序 #1.3、应用程序将文件句柄赋值给变量f
f=open('a.txt','r',encoding='utf-8') #默认打开模式就为r
#f=open(...)是由操作系统打开文件,那么如果我们没有为open指定编码,那么打开文件的默认编码很明显是操作系统说了算了,
操作系统会用自己的默认编码去打开文件,在windows下是gbk,在linux下是utf-8。
若要保证不乱码,文件以什么方式存的,就要以什么方式打开。
#2. 通过句柄对文件进行操作 data=f.read() #3. 关闭文件 f.close()
强调:文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。
由于文件读写时都有可能产生IOError
,一旦出错,后面的f.close()
就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用with
语句来自动帮我们调用close()
方法。
with open('a.txt','w') as f: pass with open('a.txt','r') as read_f,open('b.txt','w') as write_f: data=read_f.read() write_f.write(data)
二、打开文件的模式
#1. 打开文件的模式有(默认为文本模式): r ,只读模式【默认模式,文件必须存在,不存在则抛出异常】 w,只写模式【不可读;不存在则创建;存在则清空内容】 a, 之追加写模式【不可读;不存在则创建;存在则只追加内容】 #2. 对于非文本文件,我们只能使用b模式,"b"表示以字节的方式操作(而所有文件也都是以字节的形式存储的,使用这种模式无需考虑文本文件的字符编码、图片文件的jgp格式、视频文件的avi格式) rb wb ab 注:以b方式打开时,读取到的内容是字节类型,写入时也需要提供字节类型,不能指定编码 #3. 了解部分 "+" 表示可以同时读写某个文件 r+, 读写【可读,可写】 w+,写读【可读,可写】 a+, 写读【可读,可写】 x, 只写模式【不可读;不存在则创建,存在则报错】 x+ ,写读【可读,可写】 xb
A.写模式
f=open('陈粒1','w',encoding='utf8') #文件存在,先清空;文件不存在,建新文件 f.write('11111111 ') f.write('222222222 ') f.write('333 4444 555 ') f.close()
你可以反复调用write()
来写入文件,但是务必要调用f.close()
来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()
方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()
的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with
语句来得保险:
with open('陈粒1','w',encoding='utf8') as f: f.write('11111111 ') f.write('222222222 ') f.write('333 4444 555 ')
要写入特定编码的文本文件,请给open()
函数传入encoding
参数,将字符串自动转换成指定编码。
三、操作文件的方法
A.
#掌握 f.read() #读取所有内容,光标移动到文件末尾 f.readline() #读取一行内容,光标移动到第二行首部 f.readlines() #读取每一行内容,存放于列表中 f.write('1111 222 ') #针对文本模式的写,需要自己写换行符 f.write('1111 222 '.encode('utf-8')) #针对b模式的写,需要自己写换行符 f.writelines(['333 ','444 ']) #文件模式 f.writelines([bytes('333 ',encoding='utf-8'),'444 '.encode('utf-8')]) #b模式 #了解 f.readable() #文件是否可读 f.writable() #文件是否可读 f.closed #文件是否关闭 f.encoding #如果文件打开模式为b,则没有该属性 f.flush() #立刻将文件内容从内存刷到硬盘 f.name
f=open("test11.py","a",encoding="utf-8") f.write("这是a的模式") f.close()
光标会出现文本的最前面,如果是w模式的话直接就覆盖了
B.b模式
1.读
f=open("test11.py","rb") data=f.read() #字符串-----》bytes encode编码 #bytes-----》字符串 decode解码 print(data) print(data.decode('utf-8')) f.close()
以b方式打开时,读取到的内容是字节类型,写入时也需要提供字节类型,不能指定编码
2.写
f=open("test22.py","wb") f.write(bytes("1111 ",encoding="utf-8")) f.write("lala".encode('utf-8')) f.close()
写入的内容必须变成字节形式
3.文件的最后一个位置开始往后写 ab模式
如果你反复往后写,那添加的内容开始的位置也只能是第一次本来的文件的最后一个位置
D.t模式
t模式是默认模式,如果不指定任何模式,那默认就是t模式,t模式是文本模式
四、文件内光标移动的方法
A.read()
1、read(3)文件打开方式为文本模式时,代表读取3个字符
2、read(3)文件打开方式为b模式时,代表读取3个字节
3、其余的文件内光标移动都是以字节为单位如seek,tell,truncate
B.tell()
告诉当前文件光标所在的位置,因为文件存储的都是二进制的形式,utf-8一个汉字占3个字节,
一个换行占2个字节( )
f=open("seek.txt","r",encoding='utf-8',newline="") #newline="" 是表示不要忽视换行符 f.readline() print(f.tell()) #读取一行内容后,打印出光标所在的位置
C.seek()
seek有三种移动方式0,1,2,其中1和2必须在b模式下进行,但无论哪种模式,都是以bytes为单位移动的
utf-8一个汉字占3个字节,所以对汉字至少是3的倍数进行seek,否则就会报错
1.0模式
表示每次seek都是从文件开头移动 seek移动是以字节的方式进行,而0是默认从开头移动,0这种方式模式就是以b字节模式进行移动,所以不用指定b模式。
with open('seek.txt', 'r', encoding= 'utf-8') as f: print(f.tell()) # 0 刚打开文件,读取的光标位置为0 print(f.seek(10)) # 10 光标移动了10字节 print(f.tell()) # 10 print(f.seek(3)) # 3 上面已经seek 10个字节了,这里为啥不是13呢? 还是前面说的,seek默认是从文件开头移动光标的。 print(f.tell()) # 3
2. 1模式,基于相对位置
必须指定 b模式,而二进制还不能指定打开文件的编码方式
with open('seek.txt', 'rb') as f: print(f.tell()) # 0 print(f.seek(10, 1)) # 10 print(f.tell()) # 10 print(f.seek(3,1)) # 13 print(f.tell()) # 13
3. 2模式
seek.txt 里面的内容
hello
你好
123
123
# 注意,为了能看出效果,上面的换行符号用 表示,实际是不可能这样写的。
转义字符占一个字节数
在Windows中:
' ' 回车,回到当前行的行首,而不会换到下一行,如果接着输出的话,本行以前的内容会被逐一覆盖;
' ' 换行,换到当前位置的下一行,而不会回到行首;
Unix系统里,每行结尾只有“<换行>”,即" ";Windows系统里面,每行结尾是“<回车><换行>”,即“ ”;Mac系统里,每行结尾是“<回车>”,即" ";。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。
合起来叫做换行符,占两个字节
with open('seek.txt', 'rb') as f: print(f.tell()) # 0 print(f.seek(-10, 2)) # 倒序seek:从文件末尾开始往前seek10个字符 print(f.read()) # b'123 123 '
倒序seek有啥用嘞? 读日志,因为看日志永远都是看最新的日志,而最新的日子都在文件的最后面
# 1. 常规读法
with open('日志文件', 'rb') as f: data = f.readlines() print(data[-1])
#即使不考虑异常处理的问题,这个代码也不完美,因为如果文件很大,data = f.readlines()会造成很大的时间和空间开销。 # 2. 用seek去读 循环文件的推荐方式 解决的思路是用将文件指针定位到文件尾,然后从文件尾试探出一行的长度,从而读取最后一行。 f=open('日志文件','rb') for i in f: offs=-10 # # 定义一个偏移量,这个需要自己大概估算一下,日志文件行有多少个字节 while True: f.seek(offs,2) data=f.readlines() # 当data的长度大于1说明最后一行以及倒数第二行已经读出来了,然后就break if len(data) > 1: print('最后一行是%s' %(data[-1].decode('utf-8'))) break offs*=2 # 不断加大光标偏移量 f.close()
D.truncate()方法
f = open('seek.txt','r+',encoding= 'utf-8' ,newline = '') f.truncate(10) #hello 你
# 截取:代表从文件内容的开头到第十个字节 # truncate 其实是写方法,表示从0到第10个字节,这些内容截取保留下来,其他的文件内容都删除掉 # 所以,文件的打开方式不能以w+、w 的方式打开,因为w+、w 一打开就把原文件清空了,那还截取什么呢?其他任何 r+,a+等等都可以,就w+不行。
五、文件的修改
文件的数据是存放于硬盘上的,因而只存在覆盖、不存在修改这么一说,我们平时看到的修改文件,都是模拟出来的效果,具体的说有两种实现方式:
1、将硬盘存放的该文件的内容全部加载到内存,在内存中是可以修改的,修改完毕后,再由内存覆盖到硬盘(word,vim,nodpad++等编辑器)
import os with open('a.txt') as read_f,open('.a.txt.swap','w') as write_f: data=read_f.read() #全部读入内存,如果文件很大,会很卡 data=data.replace('alex','SB') #在内存中完成修改 write_f.write(data) #一次性写入新文件 os.remove('a.txt') os.rename('.a.txt.swap','a.txt')
2、将硬盘存放的该文件的内容一行一行地读入内存,修改完毕就写入新文件,最后用新文件覆盖源文件
import os with open('a.txt') as read_f,open('.a.txt.swap','w') as write_f: for line in read_f: line=line.replace('alex','SB') # 把 'alex' 字符串替换为 'SB’ write_f.write(line) os.remove('a.txt') os.rename('.a.txt.swap','a.txt')