文件相关操作及原理了解

文件操作

文件是什么?

文件是操作系统提供给我们操作硬盘的方式,我们可以通过文件来具象化的访问硬盘上的空间。

平常我们在win或者mac上,都是通过鼠标点击等方式来查看、编辑文件,而这篇文章将说明如何通过代码来实现这些文件的操作。

我们将通过代码实现:

  • 打开关闭文件
  • 读和写文件(查看和修改)
  • 以不同的模式来读写文件

打开文件

什么是打开文件

我们将硬盘中的数据文件加载到内存中的过程就是打开文件。打开文件是为了能更快速的对它进行读写。在这个过程中,文件也会占用一部分的内存资源。

相对的,关闭文件就是释放被占用的内存资源并且保存文件,即将内存中的数据保存回硬盘中指定的位置。保存文件是为了能将内存中临时的数据长存于硬盘方便以后访问。

两种打开文件的方式

  1. 方式1 open、close

    # open用某种模式和编码打开了指定路径的文件,并赋值给了变量,可以将open的结果看作文件类型的数据值。
    f = open('file-road.txt', 'r', encoding='utf8')
    # 关闭文件,将内存中的内容保存到硬盘中,并回收内存中的资源
    f.close()
    

    open括号中的三个参数分别代表:

    • 文件路径
      可以是绝对路径或者相对路径
    • 模式——下文会详细说
    • encoding 指定文本文件的编码格式
  2. 方式2 with上下文管理

    with open('file-road.txt', 'r', encoding='utf8') as f:
        pass  # 子代码块
    

    open括号中的参数与上述方式1中一致,as f指将文件类型的数据值赋值给f这个变量。

    with能按照open的方式打开文件,在子代码执行完毕后,会自动执行相应的f.close()的操作

打开文件的模式

open()中的第二个参数,决定文件以什么方式加载到内存中。

文件读写

  1. r模式

    r-read读模式,指这个文件只读

    """a.txt放在同级目录下,用相对路径法可以找到
    存储内容:
    我是你的文件向导
    这是你要读取的内容请查收
    """
    with open('a.txt', 'r', encoding='utf-8') as f:
        print(f.read())
    

    将f变量理解为文件类型的数据类型,则read就是文件类型的内置方法,指读取文件的所有数据。

    拿到文件的所有数据我们可以print看一下效果。

    # 运行结果
    我是你的文件向导
    这是你要读取的内容请查收
    

    文件路径存在时,我们可以顺利的读出文件的内容

    但是文件路径不存在时,会报错!

  2. 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,留给读者自己尝试。

  3. a模式

    a-add to 追加,在文件的末尾增加内容

    with open('a.txt', 'a', encoding='utf-8') as f:
        f.write('我也加入进来了!')
    # 运行结果
    打开a.txt文件查看,内容为:
    我又来啦!我也加入进来了!
    

    我们发现在原本文件的基础上,在末尾加入了新的内容。

    还有一个细节,它并没有像print一样自动换行,那是因为print默认以换行符结尾,而write写入的内容则不会自动带换行符。我们如果想写入多行内容则要手动加入\n

文件形式

  1. 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
    
  2. 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解释器中读
    

文件操作补充

  1. read()的参数

    一次性读取文件所有内容,并且光标停留在文件末尾,继续读取则没有内容。

    文件内容比较多的时候,该方法还可能会造成计算机内存溢出。

    # 括号内还可以填写数字,在文本模式下,表示读取几个字符。
    f.read(3)  # 这里f为t模式的文件类型,read的3表示读三个字符
    f1.read(3)  # 这里f1为b模式的文件类型,read的3表示读三个字节
    
  2. for循环读取文件
    一行行读取文件内容,避免内存溢出现象的产生。

with open('a.txt', 'r', encoding='utf8') as f:
    for line in f:  # line按行依次取f中的内容
        print(line, end='')  # 将行的内容打印出来

按行划分后,得到的字符串以\n换行符结束

  1. 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','第三行']
  1. 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模式一开启,就覆盖了原文件,验证了我们上文的说法'''
    
  2. write()
    写入数据,在w模式下可以执行,可以看做文件类型数据的内置方法。

    f.write('要写入的内容')
    # write括号中的参数就是要写入文件的内容
    
  3. 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
    
  4. writelines()
    接收一个列表,一次性将列表中所有的数据值写入

    with open('a.txt', 'w', encoding='utf8')as f:
        f.writelines(['我一马当先','我紧随其后', '我稳稳当当'])
    ###a文件内容
    我一马当先我紧随其后我稳稳当当
    ###
    

    同样要注意,列表写入并不会自动换行。

  5. flush()
    将内存中文件数据立刻刷到硬盘,等价于我们平常的快捷键ctrl + s

    之前的一系列的read和write都是对内存数据的读写。

了解:光标操作

光标又可以理解为游标,是我们读写位置的依据。我们可以通过下面的例子感知光标的存在。

### 文件a.txt
第一行
第二行
第三行
###
# 在文件打开时,光标在文件的开头
with open('a.txt', 'r', encoding='utf8')as f:
    f.readline()  # 第一行
    f.readlines()  # ['第二行\n','第三行']

读出一行后,光标停在了文件的第二行开头,紧接这readlines再读出全部内容组成的列表,而读出的内容并不包含第一行,read系列的实际含义是从当前光标的位置开始读,readlines则读从当前位置到文件结尾。

  1. 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
    
  2. 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('请输入有效的编号!')
posted @ 2022-10-09 16:32  leethon  阅读(124)  评论(0编辑  收藏  举报