Самый эффективный способ поиска последних x строк файла в python
У меня есть файл, и я не знаю, насколько он будет большой (он может быть довольно большим, но размер будет сильно отличаться). Я хочу найти последние 10 строк или около того, чтобы увидеть, соответствует ли какая-либо из них строка. Мне нужно сделать это как можно быстрее и эффективнее, и мне было интересно, есть ли что-то лучше:
s = "foo"
last_bit = fileObj.readlines()[-10:]
for line in last_bit:
if line == s:
print "FOUND"
Ответы
Ответ 1
# Tail
from __future__ import with_statement
find_str = "FIREFOX" # String to find
fname = "g:/autoIt/ActiveWin.log_2" # File to check
with open(fname, "r") as f:
f.seek (0, 2) # Seek @ EOF
fsize = f.tell() # Get Size
f.seek (max (fsize-1024, 0), 0) # Set pos @ last n chars
lines = f.readlines() # Read to end
lines = lines[-10:] # Get last 10 lines
# This returns True if any line is exactly find_str + "\n"
print find_str + "\n" in lines
# If you're searching for a substring
for line in lines:
if find_str in line:
print True
break
Ответ 2
Здесь ответ, подобный MizardX, но без его очевидной проблемы с квадратичным временем в худшем случае от повторного сканирования рабочей строки для строк новой строки в виде кусков.
По сравнению с решением activestate (которое также представляется квадратичным), это не взрывается с учетом пустого файла, и каждый ищет для чтения блока вместо двух.
По сравнению с нерестом "хвост" это самодостаточно. (Но "хвост" лучше всего, если он у вас есть.)
По сравнению с захватом нескольких kB с конца и надеясь, что это достаточно, это работает для любой длины строки.
import os
def reversed_lines(file):
"Generate the lines of file in reverse order."
part = ''
for block in reversed_blocks(file):
for c in reversed(block):
if c == '\n' and part:
yield part[::-1]
part = ''
part += c
if part: yield part[::-1]
def reversed_blocks(file, blocksize=4096):
"Generate blocks of file contents in reverse order."
file.seek(0, os.SEEK_END)
here = file.tell()
while 0 < here:
delta = min(blocksize, here)
here -= delta
file.seek(here, os.SEEK_SET)
yield file.read(delta)
Использовать его по запросу:
from itertools import islice
def check_last_10_lines(file, key):
for line in islice(reversed_lines(file), 10):
if line.rstrip('\n') == key:
print 'FOUND'
break
Изменить: изменена карта() на itertools.imap() в head(). Изменить 2: упростить reverseedblocks(). Изменить 3: избежать повторного сканирования хвоста для строк новой строки. Изменить 4: переписал reverseedlines(), потому что str.splitlines() игнорирует окончательный '\n', как заметил Брайан (спасибо).
Обратите внимание, что в очень старых версиях Python конкатенация строк в цикле здесь займет квадратичное время. CPython, по крайней мере, последние несколько лет, автоматически избегает этой проблемы.
Ответ 3
Если вы используете Python в системе POSIX, вы можете использовать "tail -10" для извлечения последних нескольких строк. Это может быть быстрее, чем писать собственный код Python, чтобы получить последние 10 строк. Вместо того, чтобы открывать файл напрямую, откройте канал из команды "tail -10 filename". Если вы уверены в выходе журнала (например, вы знаете, что существует никогда любые длинные строки длиной до сотни или тысячи символов), то с использованием одного из "read the last 2KB", Подходы, перечисленные, были бы точными.
Ответ 4
Я думаю, что чтение последних 2 КБ или около того файла должно гарантировать, что вы получите 10 строк и не должно быть слишком большим количеством ресурса.
file_handle = open("somefile")
file_size = file_handle.tell()
file_handle.seek(max(file_size - 2*1024, 0))
# this will get rid of trailing newlines, unlike readlines()
last_10 = file_handle.read().splitlines()[-10:]
assert len(last_10) == 10, "Only read %d lines" % len(last_10)
Ответ 5
Вот версия, использующая mmap
, которая кажется довольно эффективной. Большой плюс заключается в том, что mmap
будет автоматически обрабатывать требования к пейджингу в файл для вас.
import os
from mmap import mmap
def lastn(filename, n):
# open the file and mmap it
f = open(filename, 'r+')
m = mmap(f.fileno(), os.path.getsize(f.name))
nlcount = 0
i = m.size() - 1
if m[i] == '\n': n += 1
while nlcount < n and i > 0:
if m[i] == '\n': nlcount += 1
i -= 1
if i > 0: i += 2
return m[i:].splitlines()
target = "target string"
print [l for l in lastn('somefile', 10) if l == target]
Ответ 6
Думаю, я помню, как я адаптировал код из этого сообщения в блоге от Manu Garg, когда мне нужно было сделать что-то подобное.
Ответ 7
Если вы находитесь в коробке unix, os.popen("tail -10 " + filepath).readlines()
, вероятно, будет самым быстрым способом. В противном случае это зависит от того, насколько вы надежны. Предлагаемые до сих пор методы все равно будут падать, так или иначе. Для надежности и скорости в наиболее распространенном случае вы, вероятно, хотите что-то вроде логарифмического поиска: используйте file.seek для перехода к концу файла минус 1000 символов, прочитайте его, проверьте, сколько строк оно содержит, затем EOF минус 3000 символов, читать в 2000 символов, подсчитывать строки, затем EOF минус 7000, читать 4000 символов, подсчитывать строки и т.д., пока у вас не будет столько строк, сколько вам нужно. Но если вы точно знаете, что это всегда будет работать с файлами с разумной длиной строки, вам может и не понадобиться.
Вы также можете найти вдохновение в исходный код для команды unix tail
.
Ответ 8
Я столкнулся с этой проблемой, проанализировав последний час LARGE syslog файлов и использовал эту функцию, чтобы активировать сайт рецептов... (http://code.activestate.com/recipes/439045/)
!/usr/bin/env python
# -*-mode: python; coding: iso-8859-1 -*-
#
# Copyright (c) Peter Astrand <[email protected]>
import os
import string
class BackwardsReader:
"""Read a file line by line, backwards"""
BLKSIZE = 4096
def readline(self):
while 1:
newline_pos = string.rfind(self.buf, "\n")
pos = self.file.tell()
if newline_pos != -1:
# Found a newline
line = self.buf[newline_pos+1:]
self.buf = self.buf[:newline_pos]
if pos != 0 or newline_pos != 0 or self.trailing_newline:
line += "\n"
return line
else:
if pos == 0:
# Start-of-file
return ""
else:
# Need to fill buffer
toread = min(self.BLKSIZE, pos)
self.file.seek(-toread, 1)
self.buf = self.file.read(toread) + self.buf
self.file.seek(-toread, 1)
if pos - toread == 0:
self.buf = "\n" + self.buf
def __init__(self, file):
self.file = file
self.buf = ""
self.file.seek(-1, 2)
self.trailing_newline = 0
lastchar = self.file.read(1)
if lastchar == "\n":
self.trailing_newline = 1
self.file.seek(-1, 2)
# Example usage
br = BackwardsReader(open('bar'))
while 1:
line = br.readline()
if not line:
break
print repr(line)
Он работает очень хорошо и намного эффективнее, чем что-то вроде fileObj.readlines() [- 10:], что заставляет python читать весь файл в памяти, а затем отрубает последние десять строк.
Ответ 9
Вы можете прочитать куски размером 1000 байт или около того из конца файла в буфер до 10 строк.
Ответ 10
Вы также можете подсчитывать строки при обратном просмотре файла, а не гадать при смещении байта.
lines = 0
chunk_size = 1024
f = file('filename')
f.seek(0, 2)
f.seek(f.tell() - chunk_size)
while True:
s = f.read(chunk_size)
lines += s.count('\n')
if lines > NUM_OF_LINES:
break
f.seek(f.tell() - chunk_size*2)
Теперь файл находится в хорошем положении для запуска readlines()
. Вы также можете кэшировать строки, которые вы читаете в первый раз, чтобы дважды очистить одну и ту же часть файла.
Ответ 11
Я принял предложение mhawke использовать mmap
и написал версию, которая использует rfind
:
from mmap import mmap
import sys
def reverse_file(f):
mm = mmap(f.fileno(), 0)
nl = mm.size() - 1
prev_nl = mm.size()
while nl > -1:
nl = mm.rfind('\n', 0, nl)
yield mm[nl + 1:prev_nl]
prev_nl = nl + 1
def main():
# Example usage
with open('test.txt', 'r+') as infile:
for line in reverse_file(infile):
sys.stdout.write(line)
Ответ 12
прочитайте последние несколько Ks файла и разделите его на строки, чтобы вернуть только последние 10.
довольно маловероятно, чтобы начало этого куска падало на границу линии, но вы все равно отбросите первые строки.
Ответ 13
Лично у меня возникнет соблазн выйти в оболочку и вызвать tail -n10 для загрузки файла. Но тогда я не программист на Python;)
Ответ 14
Во-первых, функция, которая возвращает список:
def lastNLines(file, N=10, chunksize=1024):
lines = None
file.seek(0,2) # go to eof
size = file.tell()
for pos in xrange(chunksize,size-1,chunksize):
# read a chunk
file.seek(pos,2)
chunk = file.read(chunksize)
if lines is None:
# first time
lines = chunk.splitlines()
else:
# other times, update the 'first' line with
# the new data, and re-split
lines[0:1] = (chunk + lines[0]).splitlines()
if len(lines) > N:
return lines[-N:]
file.seek(0)
chunk = file.read(size-pos)
lines[0:1] = (chunk + lines[0]).splitlines()
return lines[-N:]
Во-вторых, функция, которая выполняет итерацию по строкам в обратном порядке:
def iter_lines_reversed(file, chunksize=1024):
file.seek(0,2)
size = file.tell()
last_line = ""
for pos in xrange(chunksize,size-1,chunksize):
# read a chunk
file.seek(pos,2)
chunk = file.read(chunksize) + last_line
# split into lines
lines = chunk.splitlines()
last_line = lines[0]
# iterate in reverse order
for index,line in enumerate(reversed(lines)):
if index > 0:
yield line
# handle the remaining data at the beginning of the file
file.seek(0)
chunk = file.read(size-pos) + last_line
lines = chunk.splitlines()
for line in reversed(lines):
yield line
В вашем примере:
s = "foo"
for index, line in enumerate(iter_lines_reversed(fileObj)):
if line == s:
print "FOUND"
break
elif index+1 >= 10:
break
Изменить: Теперь автоматическое получение размера файла
Edit2: Теперь выполняется только итерация для 10 строк.
Ответ 15
Это решение будет читать файл только один раз, но с использованием 2 указателей на объектные объекты, чтобы получить последние N строк файла без повторного чтения:
def getLastLines (path, n):
# return the las N lines from the file indicated in path
fp = open(path)
for i in range(n):
line = fp.readline()
if line == '':
return []
back = open(path)
for each in fp:
back.readline()
result = []
for line in back:
result.append(line[:-1])
return result
s = "foo"
last_bit = getLastLines(r'C:\Documents and Settings\ricardo.m.reyes\My Documents\desarrollo\tail.py', 10)
for line in last_bit:
if line == s:
print "FOUND"
Ответ 16
Возможно, это может быть полезно:
import os.path
path = 'path_to_file'
os.system('tail -n1 ' + path)