Чтение больших файлов с конца

Можно ли прочитать файл в 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 Мб, но мне просто нужно прочитать последние несколько строк. Как это сделать?

Спасибо

Ответ 1

Это не чистый 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);
?>

Ответ 2

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

$fp = @fopen($file, "r");
$pos = -2;
while (fgetc($fp) != "\n") {
    fseek($fp, $pos, SEEK_END);
    $pos = $pos - 1;
}
$lastline = fgets($fp);

Ответ 3

Это зависит от того, как вы интерпретируете "can".

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

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

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

Обновление

Здесь PHP-only способ чтения последних 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);
}

Ответ 4

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

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

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

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

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");

Ответ 6

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

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

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

Чистое 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);

Ответ 7

Вот еще одно решение. Он не имеет контроля длины строки в 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);

Ответ 8

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

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

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);

    }

Ответ 9

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

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

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

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

Ответ 10

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