需求
最近项目中有个读取文件的需求,数据量还挺大,10万行的数量级。
java 使用缓存读取文件是,会相应的创建一个内部缓冲区数组在java虚拟机内存中,因此每次处理的就是这一整块内存。
简单的想:就是如果不用缓存,每次都要硬盘–虚拟机缓存–读取;有了缓存,提前读了一段放在虚拟机缓存里,可以避免频繁将硬盘上的数据读到缓存里。
因为对内存的操作肯定是比硬盘的操作要快的。
对了,java还有映射内存,可以解决大文件读写的问题。
思路
大文件读写不能一次全部读入内存,这样会导致耗尽内存。(但是在内存允许的情况下,全部读入内存是不是速度更快??)
对于大文件可以一行一行读取,因为我们处理完这行,就可以把它抛弃。
我们也可以一段一段读取大文件,实现一种缓存处理。每次读取一段文件,将这段文件放在缓存里,然后对这段处理。这会比一行一行快些。
方法1:一行一行读取
我们可以打开一个文件,然后用for循环读取每行,比如:
def method1(newName):
s1 = time.clock()
oldLine = '0'
count = 0
for line in open(newName):
newLine = line
if (newLine != oldLine):
#判断是不是空行
if newLine.strip():
nu = newLine.split()[0]
oldLine = newLine
count += 1
print "deal %s lines" %(count)
e1 = time.clock()
print "cost time " + str(e1-s1)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
我们测试一下
fileName = 'E:\\pythonProject\\ruisi\\correct_re.txt'
method1(fileName)
- 1
- 2
输出
deal 218376 lines
cost time 0.288900734402
- 1
- 2
方法1.1 一行一行读取的变形
def method11(newName):
s1 = time.clock()
oldLine = '0'
count = 0
file = open(newName)
while 1:
line = file.readline()
if not line:
break
else:
if line.strip():
newLine = line
if (newLine != oldLine):
nu = newLine.split()[0]
oldLine = newLine
count += 1
print "deal %s lines" %(count)
e1 = time.clock()
print "cost time " + str(e1-s1)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
deal 218376 lines
cost time 0.371977884619
- 1
- 2
耗时和方法1差不多,比方法1稍微多些。
方法2:一行一行,使用fileinput模块
def method2(newName):
s1 = time.clock()
oldLine = '0'
count = 0
for line in fileinput.input(newName):
newLine = line
if newLine.strip():
if (newLine != oldLine):
nu = newLine.split()[0]
oldLine = newLine
count += 1
print "deal %s lines" %(count)
e1 = time.clock()
print "cost time " + str(e1-s1)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
deal 218376 lines
cost time 0.514534051673
- 1
- 2
这儿的耗时差不多是方法1的两倍。
借助缓存,每次读取1000行
def method3(newName):
s1 = time.clock()
file = open(newName)
oldLine = '0'
count = 0
while 1:
lines = file.readlines(10*1024)
#print len(lines)
if not lines:
break
for line in lines:
if line.strip():
newLine = line
if (newLine != oldLine):
nu = newLine.split()[0]
oldLine = newLine
count += 1
print "deal %s lines" %(count)
e1 = time.clock()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
Note
readlinessizehint() 参数是限定字节大小,不是行数。
注意默认有个内部缓冲区大小是8KB,如果设定值小于 8*1024。那么都是按照8KB来的。print len(lines)
输出大概都为290。
只有当设定值大于8KB,上面的print len(lines)
才会发生变化。
deal 218376 lines
cost time 0.296652349397
- 1
- 2
这儿的性能还没方法1,表现好。可以调整每次读取的行数,比如500,1000等等,可以达到不同的耗时。
方法4 一次性全部读到内存里
def method4(newName):
s1 = time.clock()
file = open(newName)
oldLine = '0'
count = 0
for line in file.readlines():
if line.strip():
newLine = line
if (newLine != oldLine):
nu = newLine.split()[0]
oldLine = newLine
count += 1
print "deal %s lines" %(count)
e1 = time.clock()
print "cost time " + str(e1-s1)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
输出
deal 218376 lines
cost time 0.30108883108
- 1
- 2
结论
推荐使用
with open('foo.txt', 'r') as f:
for line in f:
# do_something(line)
- 1
- 2
- 3
对于大文件可以使用索引,这个索引记录下每行开头的位置,之后就可以用file.seek()定位了。如果文件内容修改了,还需要重新建立索引。这个索引可以有很多种方法建立,但是都需要将文件遍历一次。