2010-06-11 4 views
13

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

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

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

+0

дубликата: [? Чтение последних п строк файла в Рубине] (http://stackoverflow.com/questions/754494) – hippietrail

ответ

17

Единственный правильный способ сделать это, который также работает с огромными файлами, состоит в том, чтобы читать 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 

Реализация немного наивным, но быстрый тест показывает, что нелепая разница эта простая реализация уже может сделать (тестирование с ~ 25MB файл, созданный с 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 

Конечно, other implementations уже существует. Я не пробовал, поэтому не могу сказать, что лучше.

+0

Я думаю, что вы имеете в виду 'TAIL_BUF_LENGTH = 2 ** 16' или '1 << 16', оба из которых оцениваются в' 65536' (64Ki). '2^16' является бинарным эксклюзивным, или оценивается' 18'. –

+0

Отлично работает! Эталонная разница безумна по сравнению с readlines. Возможно ли также вывести соответствующий номер строки для каждой строки в результирующем массиве? Спасибо! – ericalli

+0

@ two2twelve: Нет, это не так. * Целая цель * всего этого упражнения - прочитать файл «снизу вверх». (Ваши слова, а не мои.) Как бы вы узнали, по какой строке (которая подсчитывается из * верхнего * файла), если вы начали с * дна *? Или вы имели в виду подсчет снизу вверх? В этом случае это легко: строка в индексе 'i' в буфере является строкой' n-i' th внизу. –

3

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

0

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

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

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 
Смежные вопросы