Last Updated on
原本,我觉得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的多次结果的列表。
如上,三个函数都是常见的返回文件内容的函数,这只是常见的我们对于这几个函数的认识,下面我们就来更深入的研究一下这几个方法。
1. size参数
以上三个函数都有一个共同点,都可以传入一个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
开发一个文件句柄时,此文件句柄本身就是可迭代的,可以使用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
三种方法大致就说到这里,只要理解了指针和方法功能,就能在工作中灵活使用。
有任何问题,欢迎留言。