php 使用 fseek 逐行向后读取文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3234580/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 09:06:21  来源:igfitidea点击:

Read a file backwards line by line using fseek

phpfileiofseek

提问by Jeremy Gwa

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

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

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

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

many thanks in advance

提前谢谢了

regards

问候

Jera

杰拉

回答by Jonathan Kuhn

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()将文件作为数组读取(每行是数组中的每个元素),然后使用array_reverse()向后翻转数组并循环遍历. 或者只是做一个反向循环,你从最后开始并在每个循环中递减。

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

回答by JamieL

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:

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

My file.txt

我的文件.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.

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

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

回答by char101

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

回答by bob-the-destroyer

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 fileor 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.

真的,乔纳森·库恩 (Jonathan Kuhn) 有上面恕我直言的最佳答案。我知道的唯一一种你不会使用他的答案的情况是,如果file或喜欢的功能通过 php.ini 被禁用,但管理员忘记了 fseek,或者在打开一个大文件时只获取最后几行内容会神奇地保存这样记忆。

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.

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

回答by zvone

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.

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

回答by Paul Dixon

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

回答by BABAR

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

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

回答by rvrbk

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