Как прочитать файл снизу вверх в Ruby? - PullRequest
13 голосов
/ 11 июня 2010

Я работал над средством просмотра журнала для приложения Rails и обнаружил, что мне нужно прочитать около 200 строк файла журнала снизу вверх вместо стандартного сверху вниз.

Файлы журналов могут быть довольно большими, поэтому я уже попробовал и исключил метод IO.readlines ("log_file.log") [- 200 ..- 1].

Существуют ли другие способы чтения файла в Ruby в обратном направлении без использования плагина или гема?

Ответы [ 3 ]

17 голосов
/ 11 июня 2010

Единственный правильный способ сделать это, который также работает с огромными файлами, - это читать n байт за раз с конца, пока у вас не будет нужного количества строк.По сути, именно так работает Unix tail.

Пример реализации IO#tail(n), которая возвращает последние n строки в виде Array:

class IO
  TAIL_BUF_LENGTH = 1 << 16

  def tail(n)
    return [] if n < 1

    seek -TAIL_BUF_LENGTH, SEEK_END

    buf = ""
    while buf.count("\n") <= n
      buf = read(TAIL_BUF_LENGTH) + buf
      seek 2 * -TAIL_BUF_LENGTH, SEEK_CUR
    end

    buf.split("\n")[-n..-1]
  end
end

Реализациянемного наивный, но быстрый бенчмарк показывает, какое нелепое отличие может сделать эта простая реализация (протестировано с файлом ~ 25 МБ, сгенерированным с помощью yes > yes.txt):

                            user     system      total        real
f.readlines[-200..-1]   7.150000   1.150000   8.300000 (  8.297671)
f.tail(200)             0.000000   0.000000   0.000000 (  0.000367)

Код теста:

require "benchmark"

FILE = "yes.txt"

Benchmark.bmbm do |b|
  b.report "f.readlines[-200..-1]" do
    File.open(FILE) do |f|
      f.readlines[-200..-1]
    end
  end

  b.report "f.tail(200)" do
    File.open(FILE) do |f|
      f.tail(200)
    end
  end
end

Конечно, другие реализации уже существуют.Я не пробовал ничего, поэтому я не могу сказать вам, что лучше.

3 голосов
/ 31 июля 2011

Доступен модуль Elif (порт Perl's File :: ReadBackwards ), который обеспечивает эффективное построчное чтение файлов в обратном направлении.

1 голос
/ 14 декабря 2015

Поскольку я слишком новичок, чтобы комментировать удивительный ответ, я должен опубликовать его как отдельный ответ.Мне нужна была эта функция, чтобы читать файлы журналов во время их записи, а последняя часть журналов содержит строку, которую мне нужно знать, что она готова, и я могу начать ее анализ.

Следовательно, обработка файлов небольшого размера имеет решающее значениедля меня (я мог бы пинговать журнал, пока он крошечный).Поэтому я улучшил код Мольфа:

class IO
    def tail(n)
        return [] if n < 1
        if File.size(self) < ( 1 << 16 ) 
            tail_buf_length = File.size(self)
            return self.readlines.reverse[0..n-1]
        else 
            tail_buf_length = 1 << 16
        end
        self.seek(-tail_buf_length,IO::SEEK_END)
        out   = ""
        count = 0
        while count <= n
            buf     =  self.read( tail_buf_length )
            count   += buf.count("\n")
            out     += buf
            # 2 * since the pointer is a the end , of the previous iteration
            self.seek(2 * -tail_buf_length,IO::SEEK_CUR)
        end
        return out.split("\n")[-n..-1]
    end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...