使用 fseek 逐行向后读取文件

发布于 2024-09-08 21:08:39 字数 95 浏览 12 评论 0原文

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

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

提前非常

谢杰拉

How do I read a file backwards line by line using fseek?

code can be helpful. must be cross platform and pure php.

many thanks in advance

regards

Jera

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

黑凤梨 2024-09-15 21:08:40

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

My file.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++;
}

The question is asking using fseek, so can only assume that performance is an issue and file() is not the solution. Here is a simple approach using fseek:

My file.txt

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

And the code:

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

Output:

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"
}

You don't have to append to the $lines array like I am, you can print the output straight away if that is the purpose of your script. Also it is easy to introduce a counter if you want to limit the number of lines.

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

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

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

If you are going to read the entire file in anyways, just use file() to read the file in as an array (each line is each element in the array) and then use array_reverse() to flip the array backwards and loop through that. Or just do a reverse for loop where you start at the end and decrement on each loop.

$file = file("test.txt");
$file = array_reverse($file);
foreach($file as $f){
    echo $f."<br />";
}
盛夏已如深秋| 2024-09-15 21:08:40
<?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";
<?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";
红尘作伴 2024-09-15 21:08:40

要完全反转文件:

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

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

$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);
    if ($char === "\n") {
        // analyse completed line $output[$ln] if need be
        $ln++;
        continue;
        }
    $output[$ln] = $char . ((array_key_exists($ln, $output)) ? $output[$ln] : '');
    }
fclose($fl);
print_r($output);

不过,实际上,乔纳森·库恩(Jonathan Kuhn)在上面给出了最好的答案。据我所知,您不会使用他的答案的唯一情况是,如果通过 php.ini 禁用了 file 或类似功能,但管理员忘记了 fseek,或者在打开一个大文件时只需获取最后几行内容会通过这种方式神奇地节省内存。

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

To completely reverse a file:

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

Of course, you wanted line-by-line reversal...

$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);
    if ($char === "\n") {
        // analyse completed line $output[$ln] if need be
        $ln++;
        continue;
        }
    $output[$ln] = $char . ((array_key_exists($ln, $output)) ? $output[$ln] : '');
    }
fclose($fl);
print_r($output);

Really though, Jonathan Kuhn has the best answer IMHO above. The only cases you'd not use his answer that I know of is if file or like functions are disabled via php.ini, yet the admin forgot about fseek, or when opening a huge file just get the last few lines of contents would magically save memory this way.

Note: Error handling not included. And, PHP_EOL didn't cooperate, so I used "\n" to denote end of line instead. So, above may not work in all cases.

嘿咻 2024-09-15 21:08:40

您无法逐行进行 fseek,因为在阅读之前您不知道这些行有多长。

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

You cannot fseek line by line, because you do not know how long the lines are until you read them.

You should either read the whole file into a list of lines, or if the file is too big for that and you only need the last lines, read fixed-sized chunks from the end of the file and implement a bit more complicated logic which detects lines from such data.

千と千尋 2024-09-15 21:08:40

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

您可以从后到前对文件执行缓冲读取,如下所示:

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

Reading the entire file into an array and reversing is fine unless the file is enormous.

You could perform a buffered read of your file from back to front with something like this:

  • establish a buffer_size B - this should be longer than the longest anticipated line otherwise you'll need some logic for growing the buffer size when lines are too long
  • set offset = file length - buffer_size
  • while the offset>=0
    • read buffer_size bytes from offset
    • read a line - it will be incomplete as we'll have jumped into the middle of a line, so we want to ensure the next buffer we read ends with it. Set offset = offset - buffer_size + line length
    • discard that line, read all following lines into an array and reverse them
    • process this array to do whatever you wanted to do
云裳 2024-09-15 21:08:40

此代码向后读取文件。此代码忽略读取时的修改,例如 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);

This code read file backwards. This code ignore modifications on reading, example apache access.log new lines on procressing.

$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);
烟雨凡馨 2024-09-15 21:08:40

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

该代码是逐字记录的,因此您应该(著名的最后一句话)能够将其复制到服务器上的文件中并运行它。尽管您很可能需要更改 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;
            }
        }
    }
?>

Here's a drop in replacement(ish) for fgets($fp) called fgetsr() that reads lines from a file in reverse order.

This code is verbatim so you should (famous last words) be able to copy it into a file on your server and run it. Though you may well need to change the filename in the fopn() call.

<?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;
            }
        }
    }
?>
是伱的 2024-09-15 21:08:40

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

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

Functions to read a file line-by-line in reverse:

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

Example use case: parse a long file until some date:

$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);
箜明 2024-09-15 21:08:40

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

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

I know this has been answered already but I found another, maybe faster, way.

// 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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文