Чтение больших файлов с конца - PullRequest
12 голосов
/ 23 июня 2011

Могу ли я прочитать файл в PHP с моего конца, например, если я хочу прочитать последние 10-20 строк?

И, как я прочитал, если размер файла превышает 10 МБ, я начинаю получать ошибки.

Как я могу предотвратить эту ошибку?

Для чтения обычного файла мы используем код:

if ($handle) {
    while (($buffer = fgets($handle, 4096)) !== false) {
    $i1++;
    $content[$i1]=$buffer;
    }
    if (!feof($handle)) {
        echo "Error: unexpected fgets() fail\n";
    }
    fclose($handle);
}

Мой файл может превышать 10 МБ, но мне просто нужно прочитать последние несколько строк. Как мне это сделать?

Спасибо

Ответы [ 10 ]

17 голосов
/ 23 июня 2011

Вы можете использовать fopen и fseek для перемещения по файлу в обратном направлении от конца.Например

$fp = @fopen($file, "r");
$pos = -2;
while (fgetc($fp) != "\n") {
    fseek($fp, $pos, SEEK_END);
    $pos = $pos - 1;
}
$lastline = fgets($fp);
6 голосов
/ 23 июня 2011

Зависит от того, как вы интерпретируете «может».

Если вам интересно, можете ли вы сделать это напрямую (с помощью функции PHP), не читая все предыдущие строки, тогда ответ: Нет , вы не можете.

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

Если это действительно большой файл, я бы этого не сделал. Было бы лучше, если бы вы сканировали файл, начиная с конца, и постепенно считывали блоки с конца в файл.

Обновление

Вот PHP-только способ чтения последних n строк файла без чтения всего этого:

function last_lines($path, $line_count, $block_size = 512){
    $lines = array();

    // we will always have a fragment of a non-complete line
    // keep this in here till we have our next entire line.
    $leftover = "";

    $fh = fopen($path, 'r');
    // go to the end of the file
    fseek($fh, 0, SEEK_END);
    do{
        // need to know whether we can actually go back
        // $block_size bytes
        $can_read = $block_size;
        if(ftell($fh) < $block_size){
            $can_read = ftell($fh);
        }

        // go back as many bytes as we can
        // read them to $data and then move the file pointer
        // back to where we were.
        fseek($fh, -$can_read, SEEK_CUR);
        $data = fread($fh, $can_read);
        $data .= $leftover;
        fseek($fh, -$can_read, SEEK_CUR);

        // split lines by \n. Then reverse them,
        // now the last line is most likely not a complete
        // line which is why we do not directly add it, but
        // append it to the data read the next time.
        $split_data = array_reverse(explode("\n", $data));
        $new_lines = array_slice($split_data, 0, -1);
        $lines = array_merge($lines, $new_lines);
        $leftover = $split_data[count($split_data) - 1];
    }
    while(count($lines) < $line_count && ftell($fh) != 0);
    if(ftell($fh) == 0){
        $lines[] = $leftover;
    }
    fclose($fh);
    // Usually, we will read too many lines, correct that here.
    return array_slice($lines, 0, $line_count);
}
5 голосов
/ 23 июня 2011

Это не чистый PHP, но обычное решение - использовать команду tac , которая возвращает cat и загружает файл в обратном порядке.Используйте exec () или passthru (), чтобы запустить его на сервере, а затем прочитать результаты.Пример использования:

<?php
$myfile = 'myfile.txt';
$command = "tac $myfile > /tmp/myfilereversed.txt";
exec($command);
$currentRow = 0;
$numRows = 20;  // stops after this number of rows
$handle = fopen("/tmp/myfilereversed.txt", "r");
while (!feof($handle) && $currentRow <= $numRows) {
   $currentRow++;
   $buffer = fgets($handle, 4096);
   echo $buffer."<br>";
}
fclose($handle);
?>
3 голосов
/ 17 апреля 2015

Для Linux вы можете сделать

$linesToRead = 10;
exec("tail -n{$linesToRead} {$myFileName}" , $content); 

Вы получите массив строк в переменной $ content

Решение Pure PHP

$f = fopen($myFileName, 'r');

    $maxLineLength = 1000;  // Real maximum length of your records
    $linesToRead = 10;
    fseek($f, -$maxLineLength*$linesToRead, SEEK_END);  // Moves cursor back from the end of file
    $res = array();
    while (($buffer = fgets($f, $maxLineLength)) !== false) {
        $res[] = $buffer;
    }

    $content = array_slice($res, -$linesToRead);
3 голосов
/ 14 декабря 2012

Следующий фрагмент кода работает для меня.

$ file = popen ("tac $ filename", 'r');

while ($ line = fgets ($ file))) {

   echo $line;

}

Ссылка: http://laughingmeme.org/2008/02/28/reading-a-file-backwards-in-php/

3 голосов
/ 23 июня 2011

Если ваш код не работает и сообщает об ошибке, вы должны включить ошибку в свои сообщения!

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

Самый эффективный способ решить проблему - это, как предполагает Гриниша, искать конец файла, а затем немного вернуться назад.Но механизм Ганиши для того, чтобы вернуться немного назад, не очень эффективен.

Вместо этого рассмотрим метод получения последних нескольких строк из потока (т. Е. Там, где вы не можете искать):

while (($buffer = fgets($handle, 4096)) !== false) {
    $i1++;
    $content[$i1]=$buffer;
    unset($content[$i1-$lines_to_keep]);
}

Так что, если вы знаете, что ваша максимальная длина строки составляет 4096, то вы бы:

if (4096*lines_to_keep<filesize($input_file)) {
   fseek($fp, -4096*$lines_to_keep, SEEK_END);
}

Затем примените цикл, который я описал ранее.

Поскольку C имеет несколько более эффективных методов для работы сВ байтовых потоках самым быстрым решением (в POSIX / Unix / Linux / BSD) системы будет просто:

$last_lines=system("last -" . $lines_to_keep . " filename");
2 голосов
/ 15 сентября 2012

Вот еще одно решение. Он не имеет контроля длины строки в fgets (), вы можете добавить его.

/* Read file from end line by line */
$fp = fopen( dirname(__FILE__) . '\\some_file.txt', 'r');
$lines_read = 0;
$lines_to_read = 1000;
fseek($fp, 0, SEEK_END); //goto EOF
$eol_size = 2; // for windows is 2, rest is 1
$eol_char = "\r\n"; // mac=\r, unix=\n
while ($lines_read < $lines_to_read) {
    if (ftell($fp)==0) break; //break on BOF (beginning...)
    do {
            fseek($fp, -1, SEEK_CUR); //seek 1 by 1 char from EOF
        $eol = fgetc($fp) . fgetc($fp); //search for EOL (remove 1 fgetc if needed)
        fseek($fp, -$eol_size, SEEK_CUR); //go back for EOL
    } while ($eol != $eol_char && ftell($fp)>0 ); //check EOL and BOF

    $position = ftell($fp); //save current position
    if ($position != 0) fseek($fp, $eol_size, SEEK_CUR); //move for EOL
    echo fgets($fp); //read LINE or do whatever is needed
    fseek($fp, $position, SEEK_SET); //set current position
    $lines_read++;
}
fclose($fp);
1 голос
/ 11 октября 2017

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

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

    $fh = fopen($file, "r");
    fseek($fh, -8192, SEEK_END);
    $lines = array();
    while($lines[] = fgets($fh)) {}

Возможно, это даже более эффективно, чем ответ с наивысшим рейтингом, который считывает файл символ за символом, сравнивает каждый символ и разбивает на основе символов новой строки.

1 голос
/ 27 октября 2016

Что ж, при поиске того же самого, я могу найти следующее и подумал, что это может быть полезно и другим, поэтому поделился этим здесь:

/ * Чтение файла из конца в строку * /

function tail_custom($filepath, $lines = 1, $adaptive = true) {
        // Open file
        $f = @fopen($filepath, "rb");
        if ($f === false) return false;

        // Sets buffer size, according to the number of lines to retrieve.
        // This gives a performance boost when reading a few lines from the file.
        if (!$adaptive) $buffer = 4096;
        else $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));

        // Jump to last character
        fseek($f, -1, SEEK_END);

        // Read it and adjust line number if necessary
        // (Otherwise the result would be wrong if file doesn't end with a blank line)
        if (fread($f, 1) != "\n") $lines -= 1;

        // Start reading
        $output = '';
        $chunk = '';

        // While we would like more
        while (ftell($f) > 0 && $lines >= 0) {

            // Figure out how far back we should jump
            $seek = min(ftell($f), $buffer);

            // Do the jump (backwards, relative to where we are)
            fseek($f, -$seek, SEEK_CUR);

            // Read a chunk and prepend it to our output
            $output = ($chunk = fread($f, $seek)) . $output;

            // Jump back to where we started reading
            fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);

            // Decrease our line counter
            $lines -= substr_count($chunk, "\n");

        }

        // While we have too many lines
        // (Because of buffer size we might have read too many)
        while ($lines++ < 0) {
            // Find first newline and remove all text before that
            $output = substr($output, strpos($output, "\n") + 1);
        }

        // Close file and return
        fclose($f);     
        return trim($output);

    }
0 голосов
/ 20 января 2014

Как сказал Эйнштейн, все должно быть сделано как можно проще, но не проще.На данный момент вам нужна структура данных, структура данных LIFO или просто положить стек.

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