Самый эффективный способ поиска последних x строк файла в python - PullRequest
31 голосов
/ 04 ноября 2008

У меня есть файл, и я не знаю, насколько большим он будет (он может быть довольно большим, но размер будет сильно различаться). Я хочу найти последние 10 строк или около того, чтобы увидеть, соответствует ли какая-либо из них строке. Мне нужно сделать это как можно быстрее и эффективнее, и мне было интересно, есть ли что-нибудь лучше, чем:

s = "foo"
last_bit = fileObj.readlines()[-10:]
for line in last_bit:
    if line == s:
        print "FOUND"

Ответы [ 17 ]

1 голос
/ 24 июля 2013

Я принял предложение Мхавке об использовании 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)
0 голосов
/ 13 марта 2010

Может быть, это будет полезно:

import os.path

path = 'path_to_file'
os.system('tail -n1 ' + path)
0 голосов
/ 04 ноября 2008

Это решение будет считывать файл только один раз, но используя 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"
0 голосов
/ 04 ноября 2008

Во-первых, функция, которая возвращает список:

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 строк.

0 голосов
/ 04 ноября 2008

Лично у меня было бы желание вырваться в оболочку и вызвать tail -n10 для загрузки файла. Но тогда я не программист на Python;)

0 голосов
/ 08 августа 2018

Благодаря решению от 18 Дариуса Бэкона, но с реализацией на 30% быстрее и включением в класс io.BaseIO.

class ReverseFile(io.IOBase):
    def __init__ (self, filename, headers=1):
        self.fp = open(filename)
        self.headers = headers
        self.reverse = self.reversed_lines()
        self.end_position = -1
        self.current_position = -1

    def readline(self, size=-1):
        if self.headers > 0:
            self.headers -= 1
            raw = self.fp.readline(size)
            self.end_position = self.fp.tell()
            return raw

        raw = next(self.reverse)
        if self.current_position > self.end_position:
            return raw

        raise StopIteration

    def reversed_lines(self):
        """Generate the lines of file in reverse order.
        """
        part = ''
        for block in self.reversed_blocks():
            block = block + part
            block = block.split('\n')
            block.reverse()
            part = block.pop()
            if block[0] == '':
                block.pop(0)

            for line in block:
                yield line + '\n'

        if part:
            yield part

    def reversed_blocks(self, blocksize=0xFFFF):
        "Generate blocks of file's contents in reverse order."
        file = self.fp
        file.seek(0, os.SEEK_END)
        here = file.tell()
        while 0 < here:
            delta = min(blocksize, here)
            here -= delta
            file.seek(here, os.SEEK_SET)
            self.current_position = file.tell()
            yield file.read(delta)

Пример

rev = ReverseFile(filename)
for i, line in enumerate(rev):
        print("{0}: {1}".format(i, line.strip()))
0 голосов
/ 04 ноября 2008

прочитать последние несколько Ks файла и разбить его на строки, чтобы получить только последние 10.

маловероятно, чтобы начало этого чанка попадало на границу строки, но вы все равно откажетесь от первых строк.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...