【Python打卡日:20220510】文件访问

本文详细介绍了计算机中的I/O操作,重点讲解了文件读写的基本原理和步骤,包括如何通过操作系统提供的接口进行文件操作,以及Python中的实现方式。文件读写涉及打开文件、操作文件描述符和关闭文件。此外,还探讨了不同文件模式的区别,如r+、w+和a+,并展示了各种文件读取方法,如read、readline和readlines。最后,讨论了数据的组织方式,如一维数据和二维数据的存储和读取。
摘要由CSDN通过智能技术生成

一、I/O操作概述

I/O在计算机中是指Input/Output,也就是Stream(流)的输入和输出。这里的输入和输出是相对于内存来说的,Input Stream(输入流)是指数据从外(磁盘、网络)流进内存,Output Stream是数据从内存流出到外面(磁盘、网络)。程序运行时,数据都是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方(通常是磁盘、网络操作)就需要使用 I/O接口。

那么这个IO接口是由谁提供呢?高级编程语言中的IO操作是如何实现的呢?

操作系统是个通用的软件程序,其通用目的如下:

  • 硬件驱动
  • 进程管理
  • 内存管理
  • 网络管理
  • 安全管理
  • I/O管理

操作系统屏蔽了底层硬件,向上提供通用接口。因此,操作I/O的能力是由操作系统的提供的,每一种编程语言都会把操作系统提供的低级C接口封装起来供开发者使用,Python也不例外。

二、文件读写实现原理与操作步骤

1. 文件读写实现原理

文件读写就是一种常见的IO操作。那么根据上面的描述,可以推断python也应该封装操作系统的底层接口,直接提供了文件读写相关的操作方法。事实上,也确实如此,而且Java、PHP等其他语言也是。

那么我们要操作的对象是什么呢?我们又如何获取要操作的对象呢?

由于操作I/O的能力是由操作系统提供的,且现代操作系统不允许普通程序直接操作磁盘,所以读写文件时需要请求操作系统打开一个对象(通常被称为文件描述符--file descriptor, 简称fd),这就是我们在程序中要操作的文件对象

通常高级编程语言中会提供一个内置的函数,通过接收"文件路径"以及“文件打开模式”等参数来打开一个文件对象,并返回该文件对象的文件描述符。因此通过这个函数我们就可以获取要操作的文件对象了。这个内置函数在Python中叫open(), 在PHP中叫fopen()。

2. 文件读写操作步骤

不同的编程语言读写文件的操作步骤大体都是一样的,都分为以下几个步骤:

1)打开文件,获取文件描述符

2)操作文件描述符--读/写

3)关闭文件

只是不同的编程语言提供的读写文件的api是不一样的,有些提供的功能比较丰富,有些比较简陋。

需要注意的是:文件读写操作完成后,应该及时关闭。一方面,文件对象会占用操作系统的资源;另外一方面,操作系统对同一时间能打开的文件描述符的数量是有限制的,在Linux操作系统上可以通过ulimit -n 来查看这个显示数量。如果不及时关闭文件,还可能会造成数据丢失。因为我将数据写入文件时,操作系统不会立刻把数据写入磁盘,而是先把数据放到内存缓冲区异步写入磁盘。当调用close方法时,操作系统会保证把没有写入磁盘的数据全部写到磁盘上,否则可能会丢失数据。

三、文件操作

文件包含两种类型:文本文件二进制文件。文本文件有UTF-8编码,内容容易显示和阅读。
二进制文件有0和1比特组成,没有统一的字符编码。

无论文件创建为那种格式,都可以用“文本文件格式”或“二进制格式”打开,打开后的操作有所不同。
python对于文件的操作:打开文件→读取/写入→关闭文件

f = open("python.txt","rb") # 以二进制文件的格式打开
print(f.readline())
f.close()

b'\xe6\x96\x87\xe6\x9c\xac\xe6\x96\x87\xe4\xbb\xb6\xe4\xb8\x8e\xe4\xba\x8c\xe8\xbf\x9b\xe5\x88\xb6\xe6\x96\x87\xe4\xbb\xb6'
f = open("python.txt","rt") # 以文本文件的格式打开
print(f.readline())
f.close()

文本文件与二进制文件

【拓展】注意,实际应用中,我们经常获取文件的具体位置。此时,为了避免出现的各种error(比如文件找不到Error), 有几点注意:

(1)使用关键字 r , 限定文件路径格式识别

with open(r"C:\Users\xxxx\Desktop\e_python\py.log",mode='r', buffering= -1, encoding = 'utf-8') as f:
   for line in f:
       print (line)
       if '蜀' in line:
          #break
          pass
   else:
   	   print('文件中所有行,均打印完毕!')

 (2)使用 \\ 格式 

with open("C:\\Users\\xxxx\\Desktop\\e_python\\py.log",mode='r', buffering= -1, encoding = 'utf-8') as f:

(3)使用 / 格式

with open("C:/Users/xxxx/Desktop/e_python/py.log",mode='r', buffering= -1, encoding = 'utf-8') as f:

(4)关于文件名字

window 下,文件名字一般不带后缀,所以我们要在代码中使用完整的文件名+文件后缀格式 

3.1 打开方式模式

文件打开模式描述
r以只读模式打开文件,并将文件指针指向文件头;如果文件不存在会报错
w以只写模式打开文件,并将文件指针指向文件头;如果文件存在则将其内容清空,如果文件不存在则创建
a以只追加可写模式打开文件,并将文件指针指向文件尾部;如果文件不存在则创建
r+在r的基础上增加了可写功能
w+在w的基础上增加了可读功能
a+在a的基础上增加了可读功能
b读写二进制文件(默认是t,表示文本),需要与上面几种模式搭配使用,如ab,wb, ab, ab+(POSIX系统,包括Linux都会忽略该字符)
x创建写模式,当文件不存在时会创建,当文件存在时,会报错

思考1: r+、w+和a+都可以实现对文件的读写,那么他们有什么区别呢?

  • r+会覆盖当前文件指针所在位置的字符,如原来文件内容是"Hello,World",打开文件后写入"hi"则文件内容会变成"hillo, World"
  • w+与r+的不同是,w+在打开文件时就会先将文件内容清空,不知道它有什么用
  • a+与r+的不同是,a+只能写到文件末尾(无论当前文件指针在哪里)

思考2: 为什么要定义这些模式呢?为什么不能像我们用word打开一篇文档一样既可以读,又可以写,还可修改呢?

  • 跟安全有关,有这种观点的大部分是做运维的朋友,他们认为这就像linux上的rwx(读、写、执行)权限。
  • 跟操作系统内核管理I/O的机制有关,有这种观点的大部分是做C开发的,特别是与内核相关的开发人员。为了提高读写速度,要写入磁盘的数据会先放进内存缓冲区,之后再回写。由于可能会同时打开很多文件,当要回写数据时,需要遍历以打开的文件判断是否需要回写。他们认为如果打开文件时指定了读写模式,那么需要回写时,只要去查找以“可写模式”打开的文件就可以了。

3.2 文件的读取方法

方法含义
f.read从文件中读取整个文件的内容
f.readline从文件中读取一行内容
f.readlines从文件中读取所有行,以每行元素形成一个列表
f.seek改变当前文件操作指针的位置,括号内值为2,表示文章末尾。值为0,表示文章开头
方法描述
read()一次读取文件所有内容,返回一个str
read(size)每次最多读取指定长度的内容,返回一个str;在Python2中size指定的是字节长度,在Python3中size指定的是字符长度
readlines()一次读取文件所有内容,按行返回一个list
readline()每次从文件中只读取一行内容

 此外,还要两个与文件指针位置相关的方法

方法描述
seek(n)将文件指针移动到指定字节的位置
tell()获取当前文件指针所在字节位置

下面来看下操作实例

3.2.1. 读取指定长度的内容

Python2

  1. with open('song.txt', 'r') as f:

  2. print(f.read(12).decode('utf-8'))

输出结果:

匆匆那年

结果说明:Python2中read(size)方法的size参数指定的要读取的字节数,而song.txt文件是UTF-8编码的内容,一个汉字占3个字节,因此12个字节刚好是4个汉字。

Python3

  1. with open('song.txt', 'r', encoding='utf-8') as f:

  2. print(f.read(12))

输出结果:

匆匆那年我们 究竟说

结果说明:Python3中read(size)方法的size参数指定的要读取的字符数,这与文件的字符编码无关,就是返回12个字符。

3.3.2. 读取文件中的一行内容

Python2

  1. with open('song.txt', 'r', encoding='utf-8') as f:

  2. print(f.readline())

Python3

  1. with open('song.txt', 'r') as f:

  2. print(f.readline().decode('utf-8'))

输出结果都一样:

匆匆那年我们 究竟说了几遍 再见之后再拖延

3.2.3. 遍历打印一个文件中的每一行

这里我们只以Python3来进行实例操作,Python2仅仅是需要在读取到内容后进行手动解码而已,上面已经有示例。

方式一:先一次性读取所有行到内存,然后再遍历打印

  1. with open('song.txt', 'r', encoding='utf-8') as f:

  2. for line in f.readlines():

  3. print(line)

输出结果

  1. 匆匆那年我们 究竟说了几遍 再见之后再拖延

  2. 可惜谁有没有 爱过不是一场 七情上面的雄辩

  3. 匆匆那年我们 一时匆忙撂下 难以承受的诺言

  4. 只有等别人兑现

这种方式的缺点与read()方法是一样的,都是会消耗大量的内存空间。

方式二:通过迭代器一行一行的读取并打印

  1. with open('song.txt', 'r', encoding='utf-8', newline='') as f:

  2. for line in f:

  3.   print(line)

输出结果

  1. 匆匆那年我们 究竟说了几遍 再见之后再拖延

  2. 可惜谁有没有 爱过不是一场 七情上面的雄辩

  3. 匆匆那年我们 一时匆忙撂下 难以承受的诺言

  4. 只有等别人兑现

另外,发现上面的输出结果中行与行之间多了一个空行。这是因为文件每一行的默认都有换行符,而print()方法也会输出换行,因此就多了一个空行。去掉空行也比较简单:可以用line.rstrip()去除字符串右边的换行符,也可以通过print(line, end='')避免print方法造成的换行。

file类的其他方法:

方法描述
flush()刷新缓冲区数据,将缓冲区中的数据立刻写入文件
next()返回文件下一行,这个方法也是file对象实例可以被当做迭代器使用的原因
truncate([size])截取文件中指定字节数的内容,并覆盖保存到文件中,如果不指定size参数则文件将被清空; Python2无返回值,Python3返回新文件的内容字节数
write(str)将字符串写入文件,没有返回值
writelines(sequence)向文件写入一个字符串或一个字符串列表,如果字符串列表中的元素需要换行要自己加入换行符
fileno()返回一个整型的文件描述符,可以用于一些底层IO操作上(如,os模块的read方法)
isatty()判断文件是否被连接到一个虚拟终端,是则返回True,否则返回False

3.2.4. 举例

现以该文件来进行读取展示,
(1)

f = open('a.txt','rt')
print(f.read())

文本文件
二进制文件
文本文件与二进制文件

(2)

f = open('a.txt','rt')
print(f.readlines())

['文本文件\n', '二进制文件\n', '文本文件与二进制文件']

(3)

f = open('a.txt','rt')
print(f.readline())

文本文件

逐行读取内容并进行操作。

f = open('a.txt','rt')
f.seek(0)
for line in f:
    print(line)
f.close()

文本文件

二进制文件

文本文件与二进制文件

文件打开后,对文件的读写有一个读取指针,当从文件中读入内容后,读取指针将向前进,再次读取的内容将从指针的新位置开始。

(1)

f = open('abc.txt','w')
f.write("文本文件\n")
f.write("二进制文件\n")
f.write("文本文件与二进制文件\n")
f.close()

程序运行结果如下:注意写入的每一行后面要加上换行符。否则所有文字都连接起来。
在这里插入图片描述
(2)

f = open('abc.txt','w')
ls = ['文本文件\n','二进制文件\n','文本文件与二进制文件\n']
f.writelines(ls)
f.close()

四、数据的组织方式

组织数据可以分为一维数据、二维数据和高维数据。
在这里插入图片描述

4.1. 一维数据

a = ['北京市','上海市','重庆市','天津市']
print(len(a))

4

一维数据的存储方式为用特殊字符来分隔各数据,例如空格、逗号、换行。
其中:使用逗号分隔的为csv格式

将列表对象输出为csv格式,采用字符串的join()格式最为方便。
一维数据的写入

ls = ['北京','上海','重庆','天津']
f = open('read.csv','w')
f.write(','.join(ls)+"\n")
f.close()

运行结果如下:
在这里插入图片描述
一维数据的读取

f = open('read.csv','r')
ls = f.read().strip('\n').split(',')
f.close()
print(ls)

['北京', '上海', '重庆', '天津']

4.2. 二维数据

二维数据可以看作许多条一维数据组成,用csv格式存储。整个csv表格代表一个二维数据。
二维数据的写入

ls = [ ['指标', '2014年', '2015年', '2016年'],
       ['居民消费价格指数', '102', '101.4', '102'],
       ['食品', '103.1', '102.3', '104.6'],
       ['烟酒及用品', '994', '102.1', '101.5'],
       ['衣着', '102.4', '102.7', '101.4'],
       ['家庭设备用品', '101.2', '101', '100.5'],
       ['医疗保健和个人用品', '101.3', '102', '101.1'],
       ['交通和通信', '99.9', '98.3', '98.7'],
       ['娱乐教育文化', '101.9', '101.4', '101.6'],
       ['居住', '102', '100.7', '101.6'], ]
f = open('a.csv','w')
for row in ls:
    f.write(",".join(row)+'\n')
f.close()

运行结果如下:
在这里插入图片描述
二维文件的写入

ls = []
f = open('a.csv','r')
for line in f:
       ls.append(line.strip('\n').split(','))
f.close()
print(ls)

[['指标', '2014年', '2015年', '2016年'],
 ['居民消费价格指数', '102', '101.4', '102'], 
 ['食品', '103.1', '102.3', '104.6'], 
 ['烟酒及用品', '994', '102.1', '101.5'], 
 ['衣着', '102.4', '102.7', '101.4'],
 ['家庭设备用品', '101.2', '101', '100.5'], 
 ['医疗保健和个人用品', '101.3', '102', '101.1'],
 ['交通和通信', '99.9', '98.3', '98.7'],
 ['娱乐教育文化', '101.9', '101.4', '101.6'], 
 ['居住', '102', '100.7', '101.6']]

常见错误汇总:

错误1:

“UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte 0x80 in position 42: illegal multibyte se” 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值