Python read,readline,readlines和大文件读取
原本,我觉得read,readline,readlines比较简单,没什么好说的,本没打算要单独说一说的,但是在一次面试的时候,面试官问到了这个问题,但我并没有回答的很好,在面对大文件时的处理,没有给出很好的回答,所以这里单独来研究研究,并好好说一下这三个的方法。
首先,这三个方法都是Python中对文件的操作。可以通过with open(...) as f: 打开文件并操作文件。
正文
首先,先说一下这几个函数的作用的用法:
- read(size=-1),返回文件的全部内容,返回的数据类型根据打开的方法来定,size默认为-1,表示返回全部内容,指定size,则返回size个字符长度的内容。
- readline(size=-1),size指定返回当前行size长度的字符,当指定size时,功能跟read()相同,从当前位置返回指定长度的字符。默认为-1,表示返回当前行全部内容,也就是读取字符直到换行符,返回的内容包括换行符。
- readlines(size=-1),同上,size为字符长度,当指定size时,会返回指定长度的字符所在的所有行的全部内容,并放到列表中返回。当默认为-1时,返回此文件所有行的全部数据,并放到列表中返回。返回的数据包括换行符,就是readline的多次结果的列表。
如上,三个函数都是常见的返回文件内容的函数,这只是常见的我们对于这几个函数的认识,下面我们就来更深入的研究一下这几个方法。
以上三个函数都有一个共同点,都可以传入一个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参数的用法。
2. 指针的概念
在操作文件时,内部其实有一个指针,当开开文件时,默认指针在文件最开头,当用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()方法手动移动指针。
3. 大文件的读取
在用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('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()方法才能接受负数参数。
:

浙公网安备 33010602011771号