如何在 Python 中廉价地获取大文件的行数?
Posted
技术标签:
【中文标题】如何在 Python 中廉价地获取大文件的行数?【英文标题】:How to get line count of a large file cheaply in Python? 【发布时间】:2010-10-25 02:36:44 【问题描述】:我需要在 python 中获取一个大文件(数十万行)的行数。在内存和时间方面最有效的方法是什么?
目前我这样做:
def file_len(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
有没有可能做得更好?
【问题讨论】:
您需要精确的行数还是一个近似值就足够了? 我会在 for 循环之前添加 i=-1,因为此代码不适用于空文件。 @Legend:我敢打赌 pico 正在考虑,获取文件大小(使用 seek(0,2) 或等值),除以近似行长度。您可以在开头阅读几行来猜测平均行长。enumerate(f, 1)
并放弃 i + 1
?
@IanMackinnon 适用于空文件,但您必须在 for 循环之前将 i 初始化为 0。
【参考方案1】:
经过perfplot 分析,不得不推荐缓冲读取解决方案
def buf_count_newlines_gen(fname):
def _make_gen(reader):
while True:
b = reader(2 ** 16)
if not b: break
yield b
with open(fname, "rb") as f:
count = sum(buf.count(b"\n") for buf in _make_gen(f.raw.read))
return count
它快速且节省内存。大多数其他解决方案的速度大约慢 20 倍。
重现情节的代码:
import mmap
import subprocess
from functools import partial
import perfplot
def setup(n):
fname = "t.txt"
with open(fname, "w") as f:
for i in range(n):
f.write(str(i) + "\n")
return fname
def for_enumerate(fname):
i = 0
with open(fname) as f:
for i, _ in enumerate(f):
pass
return i + 1
def sum1(fname):
return sum(1 for _ in open(fname))
def mmap_count(fname):
with open(fname, "r+") as f:
buf = mmap.mmap(f.fileno(), 0)
lines = 0
while buf.readline():
lines += 1
return lines
def for_open(fname):
lines = 0
for _ in open(fname):
lines += 1
return lines
def buf_count_newlines(fname):
lines = 0
buf_size = 2 ** 16
with open(fname) as f:
buf = f.read(buf_size)
while buf:
lines += buf.count("\n")
buf = f.read(buf_size)
return lines
def buf_count_newlines_gen(fname):
def _make_gen(reader):
b = reader(2 ** 16)
while b:
yield b
b = reader(2 ** 16)
with open(fname, "rb") as f:
count = sum(buf.count(b"\n") for buf in _make_gen(f.raw.read))
return count
def wc_l(fname):
return int(subprocess.check_output(["wc", "-l", fname]).split()[0])
def sum_partial(fname):
with open(fname) as f:
count = sum(x.count("\n") for x in iter(partial(f.read, 2 ** 16), ""))
return count
def read_count(fname):
return open(fname).read().count("\n")
b = perfplot.bench(
setup=setup,
kernels=[
for_enumerate,
sum1,
mmap_count,
for_open,
wc_l,
buf_count_newlines,
buf_count_newlines_gen,
sum_partial,
read_count,
],
n_range=[2 ** k for k in range(27)],
xlabel="num lines",
)
b.save("out.png")
b.show()
【讨论】:
【参考方案2】:简单方法:
1)
>>> f = len(open("myfile.txt").readlines())
>>> f
430
>>> f = open("myfile.txt").read().count('\n')
>>> f
430
>>>
num_lines = len(list(open('myfile.txt')))
【讨论】:
在这个例子中文件没有关闭。 你为什么要给出 3 个选项?它们有何不同?它们各自的优点和缺点是什么?【参考方案3】:我发现你可以。
f = open("data.txt")
linecout = len(f.readlines())
会给你答案
【讨论】:
【参考方案4】:我会使用的最简单和最短的方法是:
f = open("my_file.txt", "r")
len(f.readlines())
【讨论】:
这在另一个答案中被发现为not memory efficient。【参考方案5】:大文件的替代方法是使用xreadlines():
count = 0
for line in open(thefilepath).xreadlines( ): count += 1
对于 Python 3,请参阅:What substitutes xreadlines() in Python 3?
【讨论】:
【参考方案6】:已经有很多答案了,但不幸的是,它们中的大多数只是一个几乎无法优化问题的小经济体......
我参与了几个项目,其中行数是软件的核心功能,并且尽可能快地处理大量文件至关重要。
行数的主要瓶颈是 I/O 访问,因为您需要读取每一行才能检测行返回字符,所以根本没有办法。第二个潜在瓶颈是内存管理:一次加载越多,处理速度越快,但与第一个相比,这个瓶颈可以忽略不计。
因此,除了诸如禁用 gc 收集和其他微观管理技巧之类的微小优化之外,还有 3 种主要方法可以减少行计数函数的处理时间:
硬件解决方案:主要且最明显的方式是非编程方式:购买速度非常快的 SSD/闪存硬盘驱动器。到目前为止,这是您获得最大速度提升的方法。
数据准备解决方案:如果你生成或可以修改你处理的文件的生成方式,或者如果你可以预处理它们是可以接受的,首先将行返回转换为unix样式(@ 987654321@),因为与 Windows 或 MacOS 样式相比,这将节省 1 个字符(节省不多,但很容易获得),其次,最重要的是,您可以编写固定长度的行。如果您需要可变长度,您可以随时填充较小的行。这样,您可以立即从总文件大小中计算行数,访问速度要快得多。通常,问题的最佳解决方案是对其进行预处理,使其更符合您的最终目的。
并行化 + 硬件解决方案:如果您可以购买多个硬盘(如果可能的话,还可以购买 SSD 闪存盘),那么您甚至可以通过利用并行化来存储文件,从而超越一个磁盘的速度在磁盘之间以平衡的方式(最简单的是通过总大小进行平衡),然后从所有这些磁盘并行读取。然后,您可以期望获得与您拥有的磁盘数量成比例的乘数提升。如果购买多个磁盘不是您的选择,那么并行化可能无济于事(除非您的磁盘像某些专业级磁盘一样具有多个读取头,但即使这样,磁盘的内部高速缓存和 PCB 电路也可能成为瓶颈并防止您完全并行使用所有磁头,此外,您必须为要使用的这个硬盘设计一个特定的代码,因为您需要知道确切的集群映射,以便将文件存储在不同磁头下的集群上,等等之后你可以用不同的头来阅读它们)。确实,众所周知,顺序读取几乎总是比随机读取快,并且在单个磁盘上的并行化将具有比顺序读取更类似于随机读取的性能(例如,您可以使用 CrystalDiskMark 在这两个方面测试您的硬盘驱动器速度) .
如果这些都不是一个选项,那么您只能依靠微观管理技巧将您的行计数功能的速度提高几个百分点,但不要指望任何真正重要的东西。相反,您可以预计,与您将看到的速度改进回报相比,您花在调整上的时间将不成比例。
【讨论】:
【参考方案7】:这是对其他一些答案的元评论。
行读取和缓冲\n
-counting 技术不会为每个文件返回相同的答案,因为某些文本文件在最后一行的末尾没有换行符。您可以通过检查最后一个非空缓冲区的最后一个字节来解决此问题,如果不是 b'\n'
,则添加 1。
在 Python 3 中,以文本模式和二进制模式打开文件会产生不同的结果,因为默认情况下,文本模式将 CR、LF 和 CRLF 识别为行尾(将它们全部转换为'\n'
),而在二进制模式下如果您计算b'\n'
,则仅计算 LF 和 CRLF 模式。无论您是按行阅读还是进入固定大小的缓冲区,这都适用。经典的 Mac OS 使用 CR 作为行尾;我不知道这些文件现在有多常见。
缓冲区读取方法使用与文件大小无关的有限 RAM,而行读取方法可以在最坏的情况下一次将整个文件读取到 RAM 中(特别是如果文件使用 CR 行结尾)。在最坏的情况下,它可能使用比文件大小更多的 RAM,因为动态调整行缓冲区大小以及(如果您以文本模式打开)Unicode 解码和存储会产生开销。
您可以通过预先分配一个字节数组并使用readinto
而不是read
来提高缓冲方法的内存使用率,并可能提高速度。现有答案之一(投票很少)这样做,但它有问题(它会重复计算一些字节)。
顶部缓冲区读取答案使用大缓冲区 (1 MiB)。由于操作系统预读,使用较小的缓冲区实际上可以更快。如果您一次读取 32K 或 64K,操作系统可能会在您请求之前将下一个 32K/64K 读取到缓存中,并且每次访问内核几乎都会立即返回。如果您一次读取 1 MiB,则操作系统不太可能推测性地读取整个兆字节。它可能会预读少量数据,但您仍会花费大量时间坐在内核中等待磁盘返回其余数据。
【讨论】:
【参考方案8】:使用 Numba
我们可以使用 Numba 来 JIT(及时)将我们的函数编译为机器码。 def numbacountparallel(fname) 运行速度快 2.8 倍 比问题中的 def file_len(fname) 。
注意事项:
在运行基准测试之前,操作系统已经将文件缓存到内存中,因为我在我的 PC 上没有看到太多的磁盘活动。 第一次读取文件时,时间会慢很多,使得使用 Numba 的时间优势变得微不足道。
第一次调用函数时,JIT 编译需要额外的时间。
如果我们做的不仅仅是计算行数,这将很有用。
Cython 是另一种选择。
http://numba.pydata.org/
结论
由于计数行将受 IO 限制,请使用问题中的 def file_len(fname),除非您想做的不仅仅是计数行数。
import timeit
from numba import jit, prange
import numpy as np
from itertools import (takewhile,repeat)
FILE = '../data/us_confirmed.csv' # 40.6MB, 371755 line file
CR = ord('\n')
# Copied from the question above. Used as a benchmark
def file_len(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
# Copied from another answer. Used as a benchmark
def rawincount(filename):
f = open(filename, 'rb')
bufgen = takewhile(lambda x: x, (f.read(1024*1024*10) for _ in repeat(None)))
return sum( buf.count(b'\n') for buf in bufgen )
# Single thread
@jit(nopython=True)
def numbacountsingle_chunk(bs):
c = 0
for i in range(len(bs)):
if bs[i] == CR:
c += 1
return c
def numbacountsingle(filename):
f = open(filename, "rb")
total = 0
while True:
chunk = f.read(1024*1024*10)
lines = numbacountsingle_chunk(chunk)
total += lines
if not chunk:
break
return total
# Multi thread
@jit(nopython=True, parallel=True)
def numbacountparallel_chunk(bs):
c = 0
for i in prange(len(bs)):
if bs[i] == CR:
c += 1
return c
def numbacountparallel(filename):
f = open(filename, "rb")
total = 0
while True:
chunk = f.read(1024*1024*10)
lines = numbacountparallel_chunk(np.frombuffer(chunk, dtype=np.uint8))
total += lines
if not chunk:
break
return total
print('numbacountparallel')
print(numbacountparallel(FILE)) # This allows Numba to compile and cache the function without adding to the time.
print(timeit.Timer(lambda: numbacountparallel(FILE)).timeit(number=100))
print('\nnumbacountsingle')
print(numbacountsingle(FILE))
print(timeit.Timer(lambda: numbacountsingle(FILE)).timeit(number=100))
print('\nfile_len')
print(file_len(FILE))
print(timeit.Timer(lambda: rawincount(FILE)).timeit(number=100))
print('\nrawincount')
print(rawincount(FILE))
print(timeit.Timer(lambda: rawincount(FILE)).timeit(number=100))
每个函数调用 100 次的时间(以秒为单位)
numbacountparallel
371755
2.8007332000000003
numbacountsingle
371755
3.1508585999999994
file_len
371755
6.7945494
rawincount
371755
6.815438
【讨论】:
【参考方案9】:类似于this answer 的单行bash 解决方案,使用现代subprocess.check_output
函数:
def line_count(filename):
return int(subprocess.check_output(['wc', '-l', filename]).split()[0])
【讨论】:
对于 Linux/Unix 用户来说,这个答案应该被投票到这个线程中更高的位置。尽管大多数人偏好跨平台解决方案,但这在 Linux/Unix 上是一种极好的方式。对于我必须从中采样数据的 1.84 亿行 csv 文件,它提供了最佳运行时间。其他纯 python 解决方案平均需要 100 多秒,而wc -l
的子进程调用需要约 5 秒。
shell=True
不利于安全,最好避免。【参考方案10】:
创建一个名为count.py
的可执行脚本文件:
#!/usr/bin/python
import sys
count = 0
for line in sys.stdin:
count+=1
然后将文件的内容通过管道传输到 python 脚本中:cat huge.txt | ./count.py
。管道也适用于 Powershell,因此您最终将计算行数。
对我来说,在 Linux 上它比简单的解决方案快 30%:
count=1
with open('huge.txt') as f:
count+=1
【讨论】:
如果你使用 bash,为什么不使用wc -l < huge.txt
?我还没有测试过哪个更快。
如果把它放在一个函数中,这样count
是一个本地函数会快得多。
@DanD。你的意思是?我会更新并分析它。【参考方案11】:
一线解决方案:
import os
os.system("wc -l filename")
我的sn-p:
>>> os.system('wc -l *.txt')
0 bar.txt
1000 command.txt
3 test_file.txt
1003 total
【讨论】:
好主意,但不幸的是,这在 Windows 上不起作用。 如果你想成为python的冲浪者,就和windows说再见吧。相信我,你总有一天会感谢我的。 我只是认为值得注意的是,这仅适用于 Windows。我更喜欢自己在 linux/unix 堆栈上工作,但是在编写软件时,恕我直言,应该考虑程序在不同操作系统下运行时可能产生的副作用。由于 OP 没有提及他的平台,并且如果有人通过 google 弹出该解决方案并复制它(不知道 Windows 系统可能存在的限制),我想添加注释。 您无法将os.system()
的输出保存到变量并进行后处理。
@AnSe 你是对的,但没有询问是否保存的问题。我想你正在理解上下文。【参考方案12】:
如果文件中的所有行长度相同(并且仅包含 ASCII 字符)*,您可以非常便宜地执行以下操作:
fileSize = os.path.getsize( pathToFile ) # file size in bytes
bytesPerLine = someInteger # don't forget to account for the newline character
numLines = fileSize // bytesPerLine
*我怀疑如果使用像 é 这样的 unicode 字符,则需要更多的努力来确定一行中的字节数。
【讨论】:
【参考方案13】:这是我用的,看起来很干净:
import subprocess
def count_file_lines(file_path):
"""
Counts the number of lines in a file using wc utility.
:param file_path: path to file
:return: int, no of lines
"""
num = subprocess.check_output(['wc', '-l', file_path])
num = num.split(' ')
return int(num[0])
更新:这比使用纯 python 稍微快一点,但以内存使用为代价。子进程在执行您的命令时将派生一个与父进程具有相同内存占用的新进程。
【讨论】:
顺便说一句,这当然不适用于 Windows。 核心实用程序显然为 Windows ***.com/questions/247234/… 提供了“wc”。如果您的代码最终将在 linux in prod 中运行,您也可以在 windows 框中使用 linux VM。 或 WSL,如果您只做这样的事情,强烈建议您不要使用任何虚拟机。:-)
是的,行得通。我不是 Windows 人,但从谷歌中我学到了 WSL = Linux 的 Windows 子系统 =)
python3.7:子进程返回字节,所以代码如下所示: int(subprocess.check_output(['wc', '-l', file_path]).decode("utf-8") .lstrip().split(" ")[0])【参考方案14】:
如果文件可以放入内存,那么
with open(fname) as f:
count = len(f.read().split(b'\n')) - 1
【讨论】:
【参考方案15】:def count_text_file_lines(path):
with open(path, 'rt') as file:
line_count = sum(1 for _line in file)
return line_count
【讨论】:
如果您认为这是错误的,请您解释一下它有什么问题吗?它对我有用。谢谢! 我也会对为什么这个答案被否决感兴趣。它逐行遍历文件并总结它们。我喜欢它,它简短而中肯,有什么问题?【参考方案16】:这是我使用纯 python 发现的最快的东西。 您可以通过设置缓冲区来使用所需的任何内存量,尽管 2**16 似乎是我计算机上的最佳选择。
from functools import partial
buffer=2**16
with open(myfile) as f:
print sum(x.count('\n') for x in iter(partial(f.read,buffer), ''))
我在这里找到了答案 Why is reading lines from stdin much slower in C++ than Python? 并稍微调整了一下。虽然wc -l
仍然比其他任何东西快 75% 左右,但它是一本很好的读物,可以了解如何快速计算行数。
【讨论】:
【参考方案17】:这是一个 python 程序,它使用多处理库在机器/内核之间分配行数。我的测试使用 8 核 Windows 64 服务器将 2000 万行文件的计数从 26 秒提高到 7 秒。注意:不使用内存映射会使事情变得更慢。
import multiprocessing, sys, time, os, mmap
import logging, logging.handlers
def init_logger(pid):
console_format = 'P0 %(levelname)s %(message)s'.format(pid)
logger = logging.getLogger() # New logger at root level
logger.setLevel( logging.INFO )
logger.handlers.append( logging.StreamHandler() )
logger.handlers[0].setFormatter( logging.Formatter( console_format, '%d/%m/%y %H:%M:%S' ) )
def getFileLineCount( queues, pid, processes, file1 ):
init_logger(pid)
logging.info( 'start' )
physical_file = open(file1, "r")
# mmap.mmap(fileno, length[, tagname[, access[, offset]]]
m1 = mmap.mmap( physical_file.fileno(), 0, access=mmap.ACCESS_READ )
#work out file size to divide up line counting
fSize = os.stat(file1).st_size
chunk = (fSize / processes) + 1
lines = 0
#get where I start and stop
_seedStart = chunk * (pid)
_seekEnd = chunk * (pid+1)
seekStart = int(_seedStart)
seekEnd = int(_seekEnd)
if seekEnd < int(_seekEnd + 1):
seekEnd += 1
if _seedStart < int(seekStart + 1):
seekStart += 1
if seekEnd > fSize:
seekEnd = fSize
#find where to start
if pid > 0:
m1.seek( seekStart )
#read next line
l1 = m1.readline() # need to use readline with memory mapped files
seekStart = m1.tell()
#tell previous rank my seek start to make their seek end
if pid > 0:
queues[pid-1].put( seekStart )
if pid < processes-1:
seekEnd = queues[pid].get()
m1.seek( seekStart )
l1 = m1.readline()
while len(l1) > 0:
lines += 1
l1 = m1.readline()
if m1.tell() > seekEnd or len(l1) == 0:
break
logging.info( 'done' )
# add up the results
if pid == 0:
for p in range(1,processes):
lines += queues[0].get()
queues[0].put(lines) # the total lines counted
else:
queues[0].put(lines)
m1.close()
physical_file.close()
if __name__ == '__main__':
init_logger( 'main' )
if len(sys.argv) > 1:
file_name = sys.argv[1]
else:
logging.fatal( 'parameters required: file-name [processes]' )
exit()
t = time.time()
processes = multiprocessing.cpu_count()
if len(sys.argv) > 2:
processes = int(sys.argv[2])
queues=[] # a queue for each process
for pid in range(processes):
queues.append( multiprocessing.Queue() )
jobs=[]
prev_pipe = 0
for pid in range(processes):
p = multiprocessing.Process( target = getFileLineCount, args=(queues, pid, processes, file_name,) )
p.start()
jobs.append(p)
jobs[0].join() #wait for counting to finish
lines = queues[0].get()
logging.info( 'finished Lines:'.format( time.time() - t, lines ) )
【讨论】:
如何处理比主内存大得多的文件?例如,在具有 4GB RAM 和 2 个内核的系统上的 20GB 文件 现在很难测试,但我认为它会将文件分页进出。 这是非常简洁的代码。我惊讶地发现使用多个处理器更快。我认为 IO 将成为瓶颈。在较旧的 Python 版本中,第 21 行需要 int(),例如 chunk = int((fSize / processes)) + 1 它会将所有文件加载到内存中吗?比计算机上的内存还大的大火呢? 你介意我用黑色格式化答案吗? black.vercel.app【参考方案18】:我不得不在一个类似的问题上发布此内容,直到我的声誉得分有所提高(感谢撞到我的人!)。
所有这些解决方案都忽略了一种使该程序运行得更快的方法,即使用无缓冲(原始)接口、使用字节数组和进行自己的缓冲。 (这只适用于 Python 3。在 Python 2 中,默认情况下可能使用也可能不使用原始接口,但在 Python 3 中,您将默认使用 Unicode。)
使用计时工具的修改版本,我相信以下代码比所提供的任何解决方案都更快(并且稍微更像 Python):
def rawcount(filename):
f = open(filename, 'rb')
lines = 0
buf_size = 1024 * 1024
read_f = f.raw.read
buf = read_f(buf_size)
while buf:
lines += buf.count(b'\n')
buf = read_f(buf_size)
return lines
使用单独的生成器函数,运行速度更快:
def _make_gen(reader):
b = reader(1024 * 1024)
while b:
yield b
b = reader(1024*1024)
def rawgencount(filename):
f = open(filename, 'rb')
f_gen = _make_gen(f.raw.read)
return sum( buf.count(b'\n') for buf in f_gen )
这可以完全通过使用 itertools 内联生成器表达式来完成,但是看起来很奇怪:
from itertools import (takewhile,repeat)
def rawincount(filename):
f = open(filename, 'rb')
bufgen = takewhile(lambda x: x, (f.raw.read(1024*1024) for _ in repeat(None)))
return sum( buf.count(b'\n') for buf in bufgen )
这是我的时间安排:
function average, s min, s ratio
rawincount 0.0043 0.0041 1.00
rawgencount 0.0044 0.0042 1.01
rawcount 0.0048 0.0045 1.09
bufcount 0.008 0.0068 1.64
wccount 0.01 0.0097 2.35
itercount 0.014 0.014 3.41
opcount 0.02 0.02 4.83
kylecount 0.021 0.021 5.05
simplecount 0.022 0.022 5.25
mapcount 0.037 0.031 7.46
【讨论】:
我正在处理 100Gb+ 的文件,而您的 rawgencounts 是我迄今为止看到的唯一可行的解决方案。谢谢! 是wccount
在此表中的子进程外壳wc
工具?
感谢@michael-bacon,这是一个非常好的解决方案。您可以通过使用bufgen = iter(partial(f.raw.read, 1024*1024), b'')
而不是结合takewhile
和repeat
来使rawincount
解决方案看起来不那么奇怪。
哦,部分功能,是的,这是一个不错的小调整。另外,我假设 1024*1024 会被解释器合并并被视为一个常量,但这是凭直觉而不是文档。
@MichaelBacon,用buffering=0
打开文件然后调用 read 而不是仅仅以“rb”打开文件并调用 raw.read 会更快,还是会针对同样的事情?【参考方案19】:
这段代码更短更清晰。这可能是最好的方法:
num_lines = open('yourfile.ext').read().count('\n')
【讨论】:
你也应该关闭文件。 它将整个文件加载到内存中。【参考方案20】:另一种可能性:
import subprocess
def num_lines_in_file(fpath):
return int(subprocess.check_output('wc -l %s' % fpath, shell=True).strip().split()[0])
【讨论】:
【参考方案21】:您可以通过以下方式使用os.path
模块:
import os
import subprocess
Number_lines = int( (subprocess.Popen( 'wc -l 0'.format( Filename ), shell=True, stdout=subprocess.PIPE).stdout).readlines()[0].split()[0] )
,其中Filename
是文件的绝对路径。
【讨论】:
这个答案和os.path
有什么关系?
IndexError: 列表索引超出范围【参考方案22】:
Kyle's answer
num_lines = sum(1 for line in open('my_file.txt'))
可能是最好的,替代方法是
num_lines = len(open('my_file.txt').read().splitlines())
这是两者的性能比较
In [20]: timeit sum(1 for line in open('Charts.ipynb'))
100000 loops, best of 3: 9.79 µs per loop
In [21]: timeit len(open('Charts.ipynb').read().splitlines())
100000 loops, best of 3: 12 µs per loop
【讨论】:
【参考方案23】:如果想在 Linux 中用 Python 廉价地获取行数,我推荐这种方法:
import os
print os.popen("wc -l file_path").readline().split()[0]
file_path 既可以是抽象文件路径,也可以是相对路径。希望这会有所帮助。
【讨论】:
【参考方案24】:def line_count(path):
count = 0
with open(path) as lines:
for count, l in enumerate(lines, start=1):
pass
return count
【讨论】:
【参考方案25】:print open('file.txt', 'r').read().count("\n") + 1
【讨论】:
【参考方案26】:我会使用Python的文件对象方法readlines
,如下:
with open(input_file) as foo:
lines = len(foo.readlines())
这将打开文件,在文件中创建行列表,计算列表的长度,将其保存到变量并再次关闭文件。
【讨论】:
虽然这是首先想到的方法之一,但它可能不是非常节省内存,尤其是在计算高达 10 GB 的文件中的行数时(像我一样),这是值得注意的劣势。 @TimeSheep 对于具有许多(例如,数十亿)小行的文件或具有极长行(例如,每行千兆字节)的文件,这是一个问题吗? 我问的原因是,编译器似乎应该能够通过不创建中间列表来优化它。 @dmityugov 根据 Python 文档,xreadlines
自 2.3 以来已被弃用,因为它只返回一个迭代器。 for line in file
是指定的替换。见:docs.python.org/2/library/stdtypes.html#file.xreadlines【参考方案27】:
这个单线怎么样:
file_length = len(open('myfile.txt','r').read().split('\n'))
使用此方法在 3900 行文件上计时需要 0.003 秒
def c():
import time
s = time.time()
file_length = len(open('myfile.txt','r').read().split('\n'))
print time.time() - s
【讨论】:
【参考方案28】:同样:
lines = 0
with open(path) as f:
for line in f:
lines += 1
【讨论】:
【参考方案29】:我在这个版本中获得了一个小的 (4-8%) 改进,它重用了一个常量缓冲区,因此它应该避免任何内存或 GC 开销:
lines = 0
buffer = bytearray(2048)
with open(filename) as f:
while f.readinto(buffer) > 0:
lines += buffer.count('\n')
您可以调整缓冲区大小,也许会看到一些改进。
【讨论】:
不错。要考虑不以 \n 结尾的文件,请在循环外添加 1 if buffer and buffer[-1]!='\n' 一个bug:上一轮的缓冲区可能不干净。 如果在缓冲区之间一部分以\结尾而另一部分以n开头怎么办?那会错过一个新行,我会建议变量来存储每个块的结束和开始,但这可能会增加脚本的时间 =(【参考方案30】:这个呢?
import sys
sys.stdin=open('fname','r')
data=sys.stdin.readlines()
print "counted",len(data),"lines"
【讨论】:
我不认为它解决了大文件被读入内存的事实。 打印 "counted",len(data),"lines" ^ SyntaxError: invalid syntax以上是关于如何在 Python 中廉价地获取大文件的行数?的主要内容,如果未能解决你的问题,请参考以下文章