使用 fseek 逐行向后读取文件

问题描述 投票:0回答:11

如何使用 fseek 逐行向后读取文件?

代码可能会有所帮助。必须是跨平台且纯php。

提前非常感谢

问候

杰拉

php file io fseek
11个回答
23
投票

问题是使用 fseek 询问,因此只能假设性能是一个问题,而 file() 不是解决方案。这是使用 fseek 的简单方法:

我的文件.txt

#file.txt
Line 1
Line 2
Line 3
Line 4
Line 5

代码:

<?php

$fp = fopen('file.txt', 'r');

$pos = -2; // Skip final new line character (Set to -1 if not present)

$lines = array();
$currentLine = '';

while (-1 !== fseek($fp, $pos, SEEK_END)) {
    $char = fgetc($fp);
    if (PHP_EOL == $char) {
            $lines[] = $currentLine;
            $currentLine = '';
    } else {
            $currentLine = $char . $currentLine;
    }
    $pos--;
}

$lines[] = $currentLine; // Grab final line

var_dump($lines);

输出:

array(5) {
   [0]=>
   string(6) "Line 5"
   [1]=>
   string(6) "Line 4"
   [2]=>
   string(6) "Line 3"
   [3]=>
   string(6) "Line 2"
   [4]=>
   string(6) "Line 1"
}

您不必像我一样附加到 $lines 数组,如果这是脚本的目的,您可以立即打印输出。如果你想限制行数,引入计数器也很容易。

$linesToShow = 3;
$counter = 0;
while ($counter <= $linesToShow && -1 !== fseek($fp, $pos, SEEK_END)) {
   // Rest of code from example. After $lines[] = $currentLine; add:
   $counter++;
}

23
投票

如果您要以任何方式读取整个文件,只需使用 file() 将文件作为数组读取(每一行是数组中的每个元素),然后使用 array_reverse() 翻转数组向后并循环。或者只是做一个反向 for 循环,从末尾开始并在每个循环上递减。

$file = file("test.txt");
$file = array_reverse($file);
foreach($file as $f){
    echo $f."<br />";
}

18
投票
<?php

class ReverseFile implements Iterator
{
    const BUFFER_SIZE = 4096;
    const SEPARATOR = "\n";

    public function __construct($filename)
    {
        $this->_fh = fopen($filename, 'r');
        $this->_filesize = filesize($filename);
        $this->_pos = -1;
        $this->_buffer = null;
        $this->_key = -1;
        $this->_value = null;
    }

    public function _read($size)
    {
        $this->_pos -= $size;
        fseek($this->_fh, $this->_pos);
        return fread($this->_fh, $size);
    }

    public function _readline()
    {
        $buffer =& $this->_buffer;
        while (true) {
            if ($this->_pos == 0) {
                return array_pop($buffer);
            }
            if (count($buffer) > 1) {
                return array_pop($buffer);
            }
            $buffer = explode(self::SEPARATOR, $this->_read(self::BUFFER_SIZE) . $buffer[0]);
        }
    }

    public function next()
    {
        ++$this->_key;
        $this->_value = $this->_readline();
    }

    public function rewind()
    {
        if ($this->_filesize > 0) {
            $this->_pos = $this->_filesize;
            $this->_value = null;
            $this->_key = -1;
            $this->_buffer = explode(self::SEPARATOR, $this->_read($this->_filesize % self::BUFFER_SIZE ?: self::BUFFER_SIZE));
            $this->next();
        }
    }

    public function key() { return $this->_key; }
    public function current() { return $this->_value; }
    public function valid() { return ! is_null($this->_value); }
}

$f = new ReverseFile(__FILE__);
foreach ($f as $line) echo $line, "\n";

8
投票

要完全反转文件:

$fl = fopen("\some_file.txt", "r");
for($x_pos = 0, $output = ''; fseek($fl, $x_pos, SEEK_END) !== -1; $x_pos--) {
    $输出.= fgetc($fl);
    }
fclose($fl);
print_r($输出);

当然,你想要逐行反转...


$fl = fopen("\some_file.txt", "r");
for($x_pos = 0, $ln = 0, $output = array(); fseek($fl, $x_pos, SEEK_END) !== -1; $x_pos--) {
    $char = fgetc($fl);
    如果($char ===“
”){
        // 如果需要,分析完成的行 $output[$ln]
        $ln++;
        继续;
        }
    $输出[$ln] = $char 。 ((array_key_exists($ln, $output)) ? $output[$ln] : '');
    }
fclose($fl);
print_r($输出);

实际上,乔纳森·库恩(Jonathan Kuhn)在上面给出了最佳答案。 据我所知,您不会使用他的答案的唯一情况是,如果通过 php.ini 禁用了

file
或类似功能,但管理员忘记了 fseek,或者在打开一个大文件时只获取最后几行内容这样会神奇地节省内存。

注意:不包括错误处理。 并且,PHP_EOL不配合,所以我使用了“ " 来表示行尾。因此,上述方法可能不适用于所有情况。


6
投票

你不能逐行查找,因为在你阅读它们之前你不知道这些行有多长。

您应该将整个文件读入行列表,或者如果文件太大而您只需要最后几行,则从文件末尾读取固定大小的块并实现一些更复杂的逻辑从此类数据中检测线条。


2
投票

将整个文件读入数组并反转是可以的,除非文件很大。

您可以使用如下方式从后到前执行文件的缓冲读取:

  • 建立 buffer_size B - 这应该比最长的预期行更长,否则当行太长时,您将需要一些逻辑来增加缓冲区大小
  • 设置偏移量=文件长度-buffer_size
  • 当偏移量>=0时
    • 从偏移量读取 buffer_size 字节
    • 读取一行 - 这将是不完整的,因为我们会跳到一行的中间,所以我们要确保我们读取的 next 缓冲区以其结尾。设置offset = offset - buffer_size + 行长
    • 丢弃该行,将以下所有行读入数组并反转它们
    • 处理这个数组来做你想做的事情

0
投票

此代码向后读取文件。此代码忽略读取时的修改,例如 apache access.log 处理时的新行。

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

fseek($f, 0, SEEK_END);

$pos = ftell($f);
$pos--;

while ($pos > 0) {
    $chr = fgetc($f);
    $pos --;

    fseek($f, $pos);

    if ($chr == PHP_EOL) {
        YOUR_OWN_FUNCTION($rivi);
        $rivi = NULL;
        continue;
    }

    $rivi = $chr.$rivi;
}

fclose($f);

0
投票

这里有一个名为 fgetsr() 的 fgets($fp) 替换(ish),它以相反的顺序从文件中读取行。

此代码是逐字记录的,因此您应该(著名的遗言)能够将其复制到服务器上的文件中并运行它。尽管您很可能需要更改 fopn() 调用中的文件名。

<?php
    header('Content-Type: text/plain');
    $fp = fopen('post.html', 'r');
    
    while($line = fgetsr($fp)) {
        echo $line;
    }







    // Read a line from the file but starting from the end
    //
    // @param $fp integer The file pointer
    //
    function fgetsr($fp)
    {
        // Make this variable persistent inside this function
        static $seeked;
        
        // The line buffer that will eventually be returned
        $line = '';

        // Initially seek to the end of the file
        if (!$seeked) {
            fseek($fp, -1, SEEK_END);
            $seeked = true;
        }
        
        // Loop through all of the characters in the file
        while(strlen($char = fgetc($fp)) {

            // fgetc() advances that pointer so go back TWO places
            // instead of one
            fseek($fp, -2, SEEK_CUR);

            //
            // Check for a newline (LF). If a newline is found
            // then break out of the function and return the
            // line that's stored in the buffer.
            //
            // NB The first line in the file (ie the last to
            //    be read)has a special case
            //
            if (ftell($fp) <= 0) {
                fseek($fp, 0, SEEK_SET);
                $line = fgets($fp);
                fseek($fp, 0, SEEK_SET);
                return $line;
            } else if ($char === "\n") {
                $line = strrev($line);
                return $line . "\n";
            } else {
                $line .= $char;
            }
        }
    }
?>

0
投票

反向逐行读取文件的函数:

function revfopen($filepath, $mode)
{
    $fp = fopen($filepath, $mode);
    fseek($fp, -1, SEEK_END);
    if (fgetc($fp) !== PHP_EOL) {
        fseek($fp, 1, SEEK_END);
    }

    return $fp;
}

function revfgets($fp)
{
    $s = '';
    while (true) {
        if (fseek($fp, -2, SEEK_CUR) === -1) {
            return false;
        }
        if (($c = fgetc($fp)) === PHP_EOL) {
            break;
        }
        $s = $c . $s;
    }

    return $s;
}

示例用例:解析长文件直到某个日期:

$fp = revfopen('/path/to/file', 'r');

$buffer = '';
while (($line = revfgets($fp)) !== false) {
    if (strpos($line, '05-10-2021') === 0) {
        break;
    }

    array_unshift($buffer, $line);
}

echo implode("\n", $buffer);

0
投票

这是使用缓冲区和产量的简短版本。

<?php

function get_file_lines_reversed($filename) {
    $file = fopen($filename, 'r');
    fseek($file, 0, SEEK_END);

    $buffer = null;
    $position = ftell($file);

    while ($position > 0) {
        $read_size = min(1024, $position);
        $position -= $read_size;
        fseek($file, $position);
        $buffer = fread($file, $read_size) . ($buffer ?? '');

        while (($newline_pos = strrpos($buffer, "\n")) !== false) {
            $line = substr($buffer, $newline_pos + 1);
            $buffer = substr($buffer, 0, $newline_pos);
            yield $line;
        }
    }

    if ($buffer !== null) {
        yield $buffer;
    }
    fclose($file);
}

$filename = '1.txt';
foreach (get_file_lines_reversed($filename) as $line) {
    echo "$line\n";
}

-1
投票

我知道这个问题已经得到解答,但我找到了另一种可能更快的方法。

// Read last 5000 chars of 'foo.log' 

if(file_exists('foo.log') && $file = fopen('foo.log', 'r')) {
    fseek($file, -5000, SEEK_END);

    $text = stream_get_line($file, 5000); 

    var_dump($text);

    fclose($file);
}
© www.soinside.com 2019 - 2024. All rights reserved.