赞
踩
原本,我觉得read
,readline
,readlines
比较简单,没什么好说的,本没打算要单独说一说的,但是在一次面试的时候,面试官问到了这个问题,但我并没有回答的很好,在面对大文件时的处理,没有给出很好的回答,所以这里单独来研究研究,并好好说一下这三个的方法。
首先,这三个方法都是Python中对文件的操作。可以通过with open(...) as f:
打开文件并操作文件。
首先,先说一下这几个函数的作用的用法:
如上,三个函数都是常见的返回文件内容的函数,这只是常见的我们对于这几个函数的认识,下面我们就来更深入的研究一下这几个方法。
以上三个函数都有一个共同点,都可以传入一个size的参数,用于指定返回字符的长度。这个在不同的函数中作用不同。
在read()
中,不分行,全部内容可看着一个整体,指定size
则返回这个整体的指定长度的内容。
在readline()
中,是返回当前行的,指定size
长度的内容,如果指定的长度超过当前行的总长度,则最多只会返回当前行的全部内容,并不会返回下一行的内容,可以理解为此方法在遇到换行符后,就会停止,不会再返回换行符后面的内容。
在readlines()
中,当size
的长度没有超过当前行时,列表中都只返回当前行的全部内容,如果size
超过当前行小于第二行结尾,则会返回当前行和下一行两行的全部数据,以此类推返回更多行数据。
以上时size参数在这几个函数中的使用注意事项,单说可能不好理解,举个例子,假设我们有一个文件filename,文件内容如下:
123 456 789
下面我们举例看看这几个的参数size的使用:
- with open('filename', 'rb') as f:
- print(f.read(5))
-
- # 输出: b'123\n45'
-
- with open('filename', 'rb') as f:
- print(f.readline(5))
-
- # 输出: b'123\n'
-
- with open('filename', 'rb') as f:
- print(f.readlines(5))
-
- # 输出: [b'123\n', b'456\n']
如上,大致就能明白一些size参数的用法。
在操作文件时,内部其实有一个指针,当开开文件时,默认指针在文件最开头,当用read
,readline
,readlines
等函数读取文件内容后,指针会进行相应的移动,且这写函数在读取文件内容时,则是从该指针的位置进行读取,只能返回指针之后的内容,而不是每次都从头读取。
要想搞明白文件操作,就必须理解这个指针的概念,在Python中可以通过tell()
方法返回指针当前的位置。
看下面例子:
- with open('filename', 'rb') as f:
- print(f.tell())
- print(f.read(2))
- print(f.tell())
- print(f.read(5))
- print(f.tell())
-
- # 输出:
- 0
- b'12'
- 2
- b'3\n456'
- 7
可以看到,read函数在读取了内容后,指针会有一个相应的移动,指针位置为2。
然后我们再看一下readline()
:
- with open('filename', 'rb') as f:
- print(f.tell())
- print(f.readline(2))
- print(f.tell())
- print(f.readline(5))
- print(f.tell())
-
- # 输出:
- 0
- b'12'
- 2
- b'3\n'
- 4
如上可以看出,readline()
最多只会输出当前行的内容,指针同样只移动到当前行的末尾就结束了。
在看一下readlines()
:
- with open('filename', 'rb') as f:
- print(f.tell())
- print(f.readlines(2))
- print(f.tell())
- print(f.readlines(5))
- print(f.tell())
-
- # 输出:
- 0
- [b'123\n']
- 4
- [b'456\n', b'789']
- 11
可以看到,readlines()
比较不一样,会返回当前指针所处行的所有内容,并将指针移动到行末尾,再此调用时就从指针的下一个字符开始,并且由于第二次调用时长度已经到了第三行,所以会返回两行的内容,指针相应的也就移动到了第三行的末尾。
指针在文件操作时,非常重要,还可以通过seek()
方法手动移动指针。
在用Python操作大文件时,这些方法默认都会将数据读取出来存放到内存中,所以若文件非常大,就会占用过大的内存,会导致资源不足,打开的速度慢等等问题。
所以我们来研究分析一下这几个方法在操作大文件时的情况。
read()
方法必须指定size
大小,否则不可取,默认会读取所有文件放到内存中。
- import sys
-
- with open('filename', 'rb') as f:
- s = f.read()
- print(sys.getsizeof(s))
-
- # 输出:
- 31779429
如上,如果filename是个31.8M的文件,在read
后,则会占用相同大小的内存空间内。
readline()
只读取一行数据,所以是可用的。这样不会导致内存占用过大。
readlines()
会读取所有数据行并放入列表中,同read()
一样,会占用大量内存。
- import sys
-
- with open('filename', 'rb') as f:
- s = f.readlines()
- qq = 0
- for i in s:
- qq += sys.getsizeof(i)
- print(qq)
-
- # 输出
- 31799196
可以看到readlines
和read
一样,占用大量内存。
那么在面对大文件,或不知道大小的文件时,该如何进行操作呢?下面列举几种方法来实现:
1. 文件操作句柄本身可迭代
当我们用with open
开发一个文件句柄时,此文件句柄本身就是可迭代的,可以使用for循环逐行输出,如下示例:
- with open('filename', 'rb') as f:
- for i in f:
- print(i)
-
- # 输出:
- b'123\n'
- b'456\n'
- b'789'
优点:简单,逐行输出,不会占用过多内存。
缺点:对于一些单行过大的文件,或本身就只有一行的大文件,这种方法就不可用了。
2. 分块输出
将大文件,自定义一个块的大小,然后一次只输出这么大的数据量,然后不断循环输出,我们可以通过yield
自定义一个生成器函数,然后迭代生成器,进行文件读取,如下所示:
- def load_big_file(fileobj, block=1024):
- while True:
- s = fileobj.read(block)
- if not s:
- break
- yield s
-
-
- with open('filename', 'rb') as f:
- for i in load_big_file(f):
- print(i)
-
- # 输出:
- b'123\n'
- b'456\n'
- b'789'

如上,我们定义了一个生成器函数load_big_file
,此文件返回一个生成器,用for
迭代此生成器,即可得到值。这样也可以解决读取大文件的问题。
看到上面,感觉好像很厉害,对不对,那么我们思考一下,我们一般会对大文件做什么操作?直接读取,有用处吗?没用处!所以继续
在运维工作中,最常见的大文件需要操作的就是日志文件,那么对于日志文件,我们一般可能会进行匹配,筛选,返回日志最后最新的n行内容等。
那么如何实现这些常见操作呢?
在日志文件中,每行的数据量不会过大,在进行匹配或者过滤时,则可以通过第一种方法,直接迭代,每行使用re
进行正则匹配,实现匹配,筛选。
那么返回最后几行的话,怎么实现呢?则可以通过文件指针的前向移动,来实现。看下面示例:
- def tail(filepath, n, block=-1024):
- with open(filepath, 'rb') as f:
- f.seek(0,2)
- filesize = f.tell()
- while True:
- if filesize >= abs(block):
- f.seek(block, 2)
- s = f.readlines()
- if len(s) > n:
- return s[-n:]
- break
- else:
- block *= 2
- else:
- block = -filesize
此方法就实现了返回文件最后几行的功能,但是有一点需要特别注意:
只有用b二进制的方式打开文件,才可以从后往前读!!!seek()
方法才能接受负数参数。
OK,Python的read
,readline
,readlines
三种方法大致就说到这里,只要理解了指针和方法功能,就能在工作中灵活使用。
有任何问题,欢迎留言。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。