一、概念
文件保存于硬盘,存储的数据都是101010二进制数据。
二、字符编码
2.1 概念
从明文到编码文本的转换称为“编码”,从编码文本又转回成明文则为“解码”。
字符串----->encode-------->utf-8
utf-8-------->decode---------->字符串
2.2 乱码的两种情况:
乱码一:存文件时就已经乱码
存文件时,由于文件内有各个国家的文字,我们单以shiftjis去存,
本质上其他国家的文字由于在shiftjis中没有找到对应关系而导致存储失败
但当我们硬要存的时候,编辑并不会报错(难道你的编码错误,编辑器这个软件就跟着崩溃了吗???),但毫无疑问,不能存而硬存,肯定是乱存了,即存文件阶段就已经发生乱码
而当我们用shiftjis打开文件时,日文可以正常显示,而中文则乱码了
乱码二:存文件时不乱码而读文件时乱码
存文件时用utf-8编码,保证兼容万国,不会乱码,而读文件时选择了错误的解码方式,比如gbk,则在读阶段发生乱码,读阶段发生乱码是可以解决的,选对正确的解码方式就ok了,
2.3 不通编码特性
①ascii 只支持英文字符,且占用一个字节,一个字节为8位。1byte = 8bit
>>> ord('a')
97
>>> bin(ord('a'))
'0b1100001'
②gbk 只支持中文字符,且一个字符占用两个字节,2byte= 16bit
>>> '我'.encode("gbk")
b'\xce\xd2'
>>> int.from_bytes('我'.encode('gbk'),byteorder='big',signed=False)
52946
③unicode 俗称万国码,不管英文还是中文都占3字节。3byte = 24bit
>>> '我'.encode()
b'\xe6\x88\x91'
>>> int.from_bytes('我'.encode(),byteorder='big',signed=False)
15108241
④utf-8 为了解决磁盘空间占用问题,它可以使用1~4个字节表示一个符号,出现了utf-8,中文utf-8仍占三字节,但是英文只用一个字节
>>> '我'.encode("utf-8")
b'\xe6\x88\x91'
>>> int.from_bytes('我'.encode('utf-8'),byteorder='big',signed=False)
15108241
三、文件操作
3.1 操作文件的流程
①. 打开文件,得到文件句柄并赋值给一个变量
②. 通过句柄对文件进行操作
③. 关闭文件
#获取文件句柄,其中参数1是文件的路径, 参数2是读写模式, 参数3是字符编码
>>> f = open('1.txt','w',encoding='utf-8')
#写字符操作
>>> f.write('hello world')
11
#关闭资源
>>> f.close()
>>> rf = open('1.txt','r',encoding='utf-8')
>>> rf.read()
'hello world'
>>> rf.close()
3.2 注意事项
3.2.1 回收资源
在操作完毕一个文件时,必须把与该文件的这两部分资源回收
回收方法为:f.close() 然后 del f 一般 f.close()后,系统会自动del变量名。
3.2.2 字符编码->仅限文本模式
f=open(…)是由操作系统打开文件,那么如果我们没有为open指定编码,那么打开文件的默认编码很明显是操作系统说了算了,操作系统会用自己的默认编码去打开文件,在windows下是gbk,在linux下是utf-8。
若要保证不乱码,文件以什么方式存的,就要以什么方式打开。
3.3 文件操作的各种模式
3.3.1 打开文件的模式有(默认为文本模式):
r ,只读模式【默认模式,文件必须存在,不存在则抛出异常】
w,只写模式【不可读;不存在则创建;存在则清空内容】
a, 追加写模式【不可读;不存在则创建;存在则只追加内容】
x, 只写模式【不可读;不存在则创建,存在则报错】
3.3.2 对于非文本文件,我们只能使用b模式,
"b"表示以字节的方式操作(而所有文件也都是以字节的形式存储的,使用这种模式无需考虑文本文件的字符编码、图片文件的jgp格式、视频文件的avi格式)
rb ,只读模式
wb,只写模式
ab,追加写模式
xb,只写模式
注:以b方式打开时,读取到的内容是字节类型,写入时也需要提供字节类型,不能指定编码
3.3.3 "+"形式
“+” 表示可以同时读写某个文件
r+, 读写【可读,可写】
w+,写读【可读,可写】
a+, 写读【可读,可写】
x+ ,写读【可读,可写】
3.4 文件推荐操作
3.4.1 with open操作
使用with open…可一个或多个文件使用逗号隔开 as 变量名称:
with open操作句柄,无需手工关闭资源。系统操作完毕后将自动回收。文件句柄是个可迭代对象,如果文件内容较多时,推荐使用迭代器来操作文本。
>>> with open('1.txt','r') as f:
... pass
...
>>> with open('1.txt','r') as f1 , open('2.txt','w') as f2:
... pass
...
3.4.2 eval()函数
可以把字符串里的字符转换为可执行代码,但只支持一行字符
>>> s = "1+1+1"
>>> type(s)
<class 'str'>
>>> a = eval(s) # 把字符串中表达式转成int类型 值为3
>>> type(a)
<class 'int'>
>>> d = "{'a':1,'b':2}"
>>> d
"{'a':1,'b':2}"
>>> d_e = eval(d) # 把字符串中表达式转成dict类型
>>> d_e
{'a': 1, 'b': 2}
>>> l = "[1,2,3,4,5]"
>>> l
'[1,2,3,4,5]'
>>> l_e = eval(l) # 把字符串中表达式转成list类型
>>> l_e
[1, 2, 3, 4, 5]
>>> s_s = "(1,2,3)"
>>> type(eval(s_s)) # 把字符串中表达式转成tuple类型
<class 'tuple'>
>>> s_s = "{1,2,}"
>>> type(eval(s_s)) # 把字符串中表达式转成set类型
<class 'set'>
>>> eval("sfsfasf") # 把字符串表达式无法执行,报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
NameError: name 'sfsfasf' is not defined
3.5 文件的读写操作
3.5.1 文本模式读取
注:Widnows 系统下的换行代表2个字符大小为 \r\t
一次性读取文本内容,缺点:效率低,一次性加载内存。
>>> with open("1.txt",'r',encoding='utf-8') as rf:
... print("一次性读取所有内容:: ", rf.read())
...
一次性读取所有内容:: hello world1
Hello world2
Hello world3
一次性读取文本内容,并组成列表。效率同样低下
>>> with open("1.txt",'r',encoding='utf-8') as rf:
... l = rf.readlines()
... print('l:',l)
...
l: ['hello world1\n', 'Hello world2\n', 'Hello world3']
通过迭代器模式,效率较高
>>> with open("1.txt",'r',encoding='utf-8') as rf:
... for x in rf:
... print(x)
...
hello world1
Hello world2
Hello world3
读取不存在的文件
>>> with open('adfasf.txt','r') as f:
... print(f)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'adfasf.txt'
写入一个文件
>>> with open('asdf.txt','w') as f: # 没有源文件,创建源文件,有文件则覆盖源文件
... f.write("123\n")
...
4
>>> f = open('asdf.txt','w') # 没有源文件,创建源文件,有文件则覆盖源文件
>>> f.writes(["1","2"])
>>> f.writelines(["1","2"]) # 通过列表写入数据
>>> f.close()
3.5.2 字节模式读写
>>> with open('1.txt','rb+') as f: # 通过b模式读写文件,无需编码参数
... print(f.read().decode()) # 读取字节进行解码, 没有指定编码格式,默认使用系统编码
...
hello world1
Hello world2
Hello world3
>>> with open('1.txt','ab') as f: # 通过b模式追加文件,无需编码参数
... f.write("您好".encode("utf-8")) # 写入字节进行编码, 指定utf-8
...
6
3.5.3 seek方法,文件内光标移动
fileObject.seek(offset[, whence])
-
offset: 开始的偏移量,也就是代表需要移动偏移的字节数,如果是负数表示从倒数第几位开始。
-
**whence:**可选,默认值为 0。给 offset 定义一个参数,表示要从哪个位置开始偏移;0 代表从文件开头开始算起,1 代表从当前位置开始算起,2 代表从文件末尾算起。当有换行时,会被换行截断。seek()无返回值,故值为None。
seek有三种移动方式0,1,2,其中1和2必须在b模式下进行,但无论哪种模式,都是以bytes为单位移动的
有如下文本
➜ python cat test.txt 123456789 中国 # 系统默认utf-8编码,所以总共字节数 = 9个数字*1字节+1个换行符*1字节+2个中文字符*3字节 = 16字节
Python 3.7.2 (default, Feb 12 2019, 08:15:36) [Clang 10.0.0 (clang-1000.11.45.5)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> f = open("test.txt",'r') >>> f.tell() #获取当前光标位置 0 >>> f.seek(4) #光标从开始初,偏移4字节 4 >>> f.read() #read方法读取光标剩余部分文本 '56789\n中国\n' >>> f.close()
>>> f = open("test.txt",'rb') >>> f.seek(-1) #直接偏移倒数第一字节,报错 Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 22] Invalid argument >>> f.seek(-12,2) #直接偏移倒数第12个字节,报错 5 >>> f.seek(2,1) #直接偏移倒数第一字节,报错 7 >>> f.seek(-1,2) #直接偏移倒数第一字节,报错 16 >>> f.seek(-10) #直接从开始位置偏移倒数第10个字节,报错 Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 22] Invalid argument >>> f.seek(-10,2) 7 >>> print(f.read().decode()) 89 中国 >>> f.close()
3.5.4 truncate方法,截断文本
truncate(n): 从文件的首行首字符开始截断,截断文件为n个字符;无n表示从当前位置起截断;截断之后n后面的所有字符被删除。其中win下的换行代表2个字符大小。
但是不能用w或w+等方式打开,因为那样直接清空文件了,所以truncate要在r+或a或a+等模式下测试效果
Last login: Thu May 9 19:05:37 on ttys000
➜ python cat test.txt
123456789
中国
>>> f = open("test.txt",'rb+')
>>> f.truncate(5)
5
>>> print(f.read().decode())
12345
>>> f.close()
3.6 例子:查看日志最后一行记录,使用高效方式
优势,日志文件如果很大的话。不会一次性加载到内存中,会根据偏移量慢慢读取
# 日志最后一行记录
➜ python tail -1 api.debug.log
2019-05-09 15:14:04.184 |-DEBUG [http-nio-8081-exec-9] com.xxx.course.config.AuthenticationInterceptor [74] -| url:http://test-apicourse.hello.cn/ method:GET
>>> def read_last_line(x):
... with open(x,'rb') as f: #通过b模式读取,拿到句柄
... init_num = -100 #初始偏移值 100字节
... while 1:
... f.seek(init_num,2) #光标从文件末尾偏移
... l = f.readlines() #读取光标后面内容,转成列表
... if len(l)>=2: #如果列表元素>=2 说明读取行数超过2行
... print("最后一行是: %s" % l[-1].decode())
... break
... else: #否则增加偏移量,继续循环
... init_num*=2
...
>>> read_last_line("api.debug.log")
最后一行是: 2019-05-09 15:14:04.184 |-DEBUG [http-nio-8081-exec-9] com.xxx.course.config.AuthenticationInterceptor [74] -| url:http://test-apicourse.hello.cn/ method:GET
3.7 例子:有如下数据文件,求男女比例各占多少
➜ python cat person_data.txt
{'name':'人员1','性别':'男'}
{'name':'人员2','性别':'男'}
{'name':'人员3','性别':'男'}
{'name':'人员4','性别':'男'}
{'name':'人员5','性别':'男'}
{'name':'人员6','性别':'女'}
{'name':'人员7','性别':'女'}
{'name':'人员8','性别':'女'}
{'name':'人员9','性别':'女'}%
# 通过r来进行特殊字符处理,使得路径合法
res_dic = {'男': 0, '女': 0} # 初始男女
with open(r'/Users/qinjie/doc/code/note/python/person_data.txt', 'rb') as read_f:
for i in read_f: # 直接for循环操作句柄,不会一次性加载到内存中
dic_line = eval(i.decode()) # 使用b模式需要解码操作,并转成字典
if dic_line['性别'] == '男':
res_dic['男'] += 1
else:
res_dic['女'] += 1
print('男性人数:%s' % res_dic['男'])
print('男性占比:{:.2%}'.format(res_dic['男']/sum(res_dic.values())))
print('女性人数:%s' % res_dic['女'])
print('女性占比:{:.2%}'.format(res_dic['女']/sum(res_dic.values())))
# 打印
# 男性人数:5
# 男性占比:55.56%
# 女性人数:4
# 女性占比:44.44%