Как прочитать файл снизу вверх в Ruby?
Я работаю над средством просмотра журналов для приложения Rails и обнаружил, что мне нужно прочитать около 200 строк файла журнала снизу вверх, а не по умолчанию сверху вниз.
Файлы журналов могут быть довольно большими, поэтому я уже пытался и исключил метод IO.readlines( "log_file.log" ) [- 200..- 1].
Есть ли какие-либо другие способы чтения файла в Ruby без необходимости в плагине или драгоценности?
Ответы
Ответ 1
Единственный правильный способ сделать это, который также работает с огромными файлами, - это считывать 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
Конечно, другие реализации уже существуют. Я не пробовал, поэтому я не могу сказать вам, что лучше.
Ответ 2
Там доступен модуль Elif (порт Perl Файл:: ReadBackwards), который делает эффективное по очереди чтение файлов в обратном порядке.
Ответ 3
Так как я слишком новичок, чтобы прокомментировать 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