文件相关操作及原理了解
文件操作
文件是什么?
文件是操作系统提供给我们操作硬盘的方式,我们可以通过文件来具象化的访问硬盘上的空间。
平常我们在win或者mac上,都是通过鼠标点击等方式来查看、编辑文件,而这篇文章将说明如何通过代码来实现这些文件的操作。
我们将通过代码实现:
- 打开关闭文件
- 读和写文件(查看和修改)
- 以不同的模式来读写文件
打开文件
什么是打开文件
我们将硬盘中的数据文件加载到内存中的过程就是打开文件。打开文件是为了能更快速的对它进行读写。在这个过程中,文件也会占用一部分的内存资源。
相对的,关闭文件就是释放被占用的内存资源并且保存文件,即将内存中的数据保存回硬盘中指定的位置。保存文件是为了能将内存中临时的数据长存于硬盘方便以后访问。
两种打开文件的方式
-
方式1 open、close
# open用某种模式和编码打开了指定路径的文件,并赋值给了变量,可以将open的结果看作文件类型的数据值。 f = open('file-road.txt', 'r', encoding='utf8') # 关闭文件,将内存中的内容保存到硬盘中,并回收内存中的资源 f.close()
open括号中的三个参数分别代表:
- 文件路径
可以是绝对路径或者相对路径 - 模式——下文会详细说
- encoding 指定文本文件的编码格式
- 文件路径
-
方式2 with上下文管理
with open('file-road.txt', 'r', encoding='utf8') as f: pass # 子代码块
open括号中的参数与上述方式1中一致,as f指将文件类型的数据值赋值给f这个变量。
with能按照open的方式打开文件,在子代码执行完毕后,会自动执行相应的
f.close()
的操作
打开文件的模式
open()中的第二个参数,决定文件以什么方式加载到内存中。
文件读写
-
r模式
r-read读模式,指这个文件只读
"""a.txt放在同级目录下,用相对路径法可以找到 存储内容: 我是你的文件向导 这是你要读取的内容请查收 """ with open('a.txt', 'r', encoding='utf-8') as f: print(f.read())
将f变量理解为文件类型的数据类型,则read就是文件类型的内置方法,指读取文件的所有数据。
拿到文件的所有数据我们可以print看一下效果。
# 运行结果 我是你的文件向导 这是你要读取的内容请查收
文件路径存在时,我们可以顺利的读出文件的内容
但是文件路径不存在时,会报错!
-
w模式
w-write写模式,只写,指往文件中写入内容
# 针对文件是否存在的两种情况讨论 - 文件不存在 with open('a.txt', 'w', encoding='utf-8') as f: f.write('我来啦!') # 运行结果:同级目录下产生了新的文件,并写入了'我来啦!'的内容 - 文件存在 with open('a.txt', 'w', encoding='utf-8') as f: f.write('我又来啦!') # 运行结果:同级目录的a.txt依然存在,打开查看,发现原本的文本被替换成了'我又来啦!'
write也是文件类型的内置方法之一,括号内为想要写入的内容。
根据上述两种情形的讨论,我们可以总结出,写模式下,文件不存在时则产生新的文件,如果文件原本存在,则会覆盖原本的文件。
ps:是写模式触发的新建文件和覆盖,而不是write,留给读者自己尝试。
-
a模式
a-add to 追加,在文件的末尾增加内容
with open('a.txt', 'a', encoding='utf-8') as f: f.write('我也加入进来了!') # 运行结果 打开a.txt文件查看,内容为: 我又来啦!我也加入进来了!
我们发现在原本文件的基础上,在末尾加入了新的内容。
还有一个细节,它并没有像print一样自动换行,那是因为print默认以换行符结尾,而write写入的内容则不会自动带换行符。我们如果想写入多行内容则要手动加入
\n
。
文件形式
-
t模式,文本形式,也是默认模式
其实上述的r、w、a模式的完整形式是rt、wt、at
在t模式下,硬盘中的数据将以文本的形式加载到内存中,需要注意:
- t模式只能编辑文本文件,其他文件会报错
- t模式下,单位是单个字符,如一个英文字母、一个汉字
- t模式下必须指定encoding字符编码格式
with open('a.txt', 'rt', encoding='utf8') as f: # r等同于rt,t模式下必写encoding pass
-
b模式,二进制形式
配合r、w、a的形式是rb、wb、ab
- b模式可以编辑所有的文件,但是是以二进制的形式加载到内存中
- b模式下,单位是单个字节,1bytes
- b模式下不可以指定encoding,因为那是指定字符编码格式的
with open('a.txt', 'rb') as f: # b模式一定不能写encoding print(f.read()) # 默认读出来是二进制形式 with open('a.txt', 'rb') as f: print(f.read().decode('utf8')) # 将read出来的内容按照utf8解码一下,可以读原本的文件 with open('b.jpg', 'rb') as f: # 也可以读图片 print(f.read()) # 但是想查看图片必须通过图片软件,不能在python解释器中读
文件操作补充
-
read()的参数
一次性读取文件所有内容,并且光标停留在文件末尾,继续读取则没有内容。
文件内容比较多的时候,该方法还可能会造成计算机内存溢出。
# 括号内还可以填写数字,在文本模式下,表示读取几个字符。 f.read(3) # 这里f为t模式的文件类型,read的3表示读三个字符 f1.read(3) # 这里f1为b模式的文件类型,read的3表示读三个字节
-
for循环读取文件
一行行读取文件内容,避免内存溢出现象的产生。
with open('a.txt', 'r', encoding='utf8') as f:
for line in f: # line按行依次取f中的内容
print(line, end='') # 将行的内容打印出来
按行划分后,得到的字符串以\n
换行符结束
- readline()和readlines()
### 文件a.txt
第一行
第二行
第三行
###
# 一次只读一行内容
with open('a.txt', 'r', encoding='utf8')as f:
f.readline() # 第一行
# 一次性读取文件内容,会按照行数组织成列表的一个个数据值
with open('a.txt', 'r', encoding='utf8')as f:
f.readlines() # ['第一行\n','第二行\n','第三行']
-
readable()
判断文件是否具备读数据的能力with open('a.txt', 'r', encoding='utf8')as f: f.readable() # True with open('a.txt', 'w', encoding='utf8')as f: f.readable() # False '''补充:执行完上述语句后,a文件的内容变空了,因为w模式一开启,就覆盖了原文件,验证了我们上文的说法'''
-
write()
写入数据,在w模式下可以执行,可以看做文件类型数据的内置方法。f.write('要写入的内容') # write括号中的参数就是要写入文件的内容
-
writeable()
判断文件是否具备写数据的能力with open('a.txt', 'r', encoding='utf8')as f: f.writeable() # False with open('a.txt', 'w', encoding='utf8')as f: f.writeable() # True
-
writelines()
接收一个列表,一次性将列表中所有的数据值写入with open('a.txt', 'w', encoding='utf8')as f: f.writelines(['我一马当先','我紧随其后', '我稳稳当当']) ###a文件内容 我一马当先我紧随其后我稳稳当当 ###
同样要注意,列表写入并不会自动换行。
-
flush()
将内存中文件数据立刻刷到硬盘,等价于我们平常的快捷键ctrl + s之前的一系列的read和write都是对内存数据的读写。
了解:光标操作
光标又可以理解为游标,是我们读写位置的依据。我们可以通过下面的例子感知光标的存在。
### 文件a.txt
第一行
第二行
第三行
###
# 在文件打开时,光标在文件的开头
with open('a.txt', 'r', encoding='utf8')as f:
f.readline() # 第一行
f.readlines() # ['第二行\n','第三行']
读出一行后,光标停在了文件的第二行开头,紧接这readlines再读出全部内容组成的列表,而读出的内容并不包含第一行,read系列的实际含义是从当前光标的位置开始读,readlines则读从当前位置到文件结尾。
-
f.seek( ) —— 移动光标
seek会基于文件的某个位置,移动光标,光标移动的单位只有字节,无论在b模式还是t模式下 f.seek(偏移量,坐标系编号) - 坐标系编号有3个: """ 0:文件开头 1:光标现在所在位置 2:光标现在 """ - 偏移量为正数则向后移动几个字节,为负则向前移动,为0不移动 ###a文件 汉字一个字符三个字节 ### with open('a.txt', 'r', encoding='utf8')as f: f.read() # hello world f.read() # '' f.seek(0,0) # 光标移动到开头 f.read() # hello world
-
f.tell( ) —— 拿到光标坐标
###a文件 汉字一个字符三个字节 ### with open('a.txt', 'r', encoding='utf8')as f: f.read() # 汉字一个字符三个字节 f.tell() # 30
文件在硬盘上的存在形式及修改的底层原理
文件在硬盘上实际上就是一串二进制,而且记录了在硬盘上的位置,但是对于每一比特(bit)的硬盘空间而言,它只有两个状态,1或者0,所以无论硬盘上有没有文件,每个比特位都会保持在一个1或者0的状态。
所谓文件就是将硬盘上的某一块区域的标记出来,进行占用,在文件占用的这块硬盘空间,不能再被挪用了。
而当文件被写入硬盘时,除了将数据记录到硬盘的某个位置以外,还要画地为牢,标记这块区域,并只能通过打开文件等方式查看和编辑文件,不能其他方式不小心覆盖掉数据。
而删除文件时,就比较偷懒,操作系统会直接解除对文件原本占用的硬盘空间的限制,变得可以利用,但是上面的数据还是以原本的二进制排列放在原本的位置。利用这点,我们就可以恢复一些数据,也涉及到已经被删除文件的安全问题,所以在删除文件时,旧硬盘不要随便扔,随便填点无关紧要的东西覆盖一下原本的数据是必要的。
编辑文件时,有两种修改的策略:
-
直接在硬盘原本的位置覆盖掉源文件
with open(r'a.txt', 'r', encoding='utf8') as f: data = f.read() with open(r'a.txt', 'w', encoding='utf8') as f1: f1.write(data.replace('jason', 'tony'))
-
在硬盘另外的位置写入一份,然后释放(删除)源文件,再快速的将命名改成源文件
import os # 后续才会学习到的模块,按照注释理解功能即可 with open('a.txt', 'r', encoding='utf8') as read_f, \ open('.a.txt.swap', 'w', encoding='utf-8') as write_f: for line in read_f: write_f.write(line.replace('tony', 'lalisa')) os.remove('a.txt') # 删除a.txt os.rename('.a.txt.swap', 'a.txt') # 重命名文件
练习
练习一
编写简易版本的拷贝工具
- 自己输入想要拷贝的数据路径,自己输入拷贝到哪个地方的目标路径
- 可以拷贝所有的文件类型
- 可能有c盘需要管理员权限不好访问,可以换个文件路径尝试
source_file = input('源文件路径及名称:')
copy_file = input('副本文件路径及名称:')
with open(rf'{source_file}', 'rb')as f1,\
open(rf'{copy_file}', 'wb')as f2:
f2.write(f1.read())
练习二
利用文件充当数据库编写用户数据库编写用户登录、注册功能
- 用户信息暂时用 用户|密码 格式
- 用户注册功能要求不能注册用户名相同的用户
while True:
choice = input('1注册|2登录|q退出:')
if choice == '1':
# 注册功能
while True:
# 输入用户名和密码
username = input('注册的用户名:')
password = input('请输入密码:')
# 判断用户名是否存在
with open(r'userinfo.txt', 'r', encoding='utf8') as f:
for line in f:
user, pwd = line.strip().split('|')
if user == username:
print('用户名已经存在,请更换用户名')
break
else:
# 注册,把用户和密码存到文件里
with open(r'userinfo.txt', 'a', encoding='utf8') as f1:
f1.write(f'{username}|{password}\n')
print(f'用户{username}注册成功')
break
elif choice == '2':
# 登录功能
while True:
# 输入用户密码
username = input('请输入用户名:')
password = input('请输入密码:')
# 核验密码登录
with open(r'userinfo.txt', 'r',encoding='utf8')as f:
for line in f:
user, pwd = line.strip().split('|')
if user == username and pwd == password:
print('登录成功')
break
else:
print('用户名或密码输入错误,请重新输入')
break
pass
elif choice == 'q':
break
else:
print('请输入有效的编号!')
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构