如何使用 fseek 逆序逐行读取文件?
请提供可跨平台且纯 PHP 的代码。
非常感谢!
祝好
Jera
如果你要读取整个文件,那么可以使用file()将文件读入一个数组中(每一行是数组的一个元素),然后使用array_reverse()将数组翻转并循环遍历。或者只需使用逆序for循环,从末尾开始循环并在每次循环中递减。
$file = file("test.txt");
$file = array_reverse($file);
foreach($file as $f){
echo $f."<br />";
}
$i >= 0
。 - Jonathan Kuhnarray_slice
和 array_reverse
只是在它们都处理数组方面有关联。它们实际上并不做同样的事情。这里的 array_reverse
只是为了可读性。如果你不想要它,因为 file
生成一个数字数组,你可以使用一个 for 循环来递减迭代器,像这样 for($i=count($file)-1; $i>=0; $i--){ $line = $file[$i]; /*do something*/ }
。 - Jonathan Kuhn#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++;
}
<?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";
$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") { // 分析完成的一行内容 $output[$ln] $ln++; continue; } $output[$ln] = $char . ((array_key_exists($ln, $output)) ? $output[$ln] : ''); } fclose($fl); print_r($output);实际上,Jonathan Kuhn 提供的答案是我认为最好的。我所知道的唯一不使用他的答案的情况是,如果通过php.ini禁用了
file
或类似函数,但管理员忘记了fseek,或者当打开一个巨大的文件只获取最后几行内容时,这种方法可以神奇地节省内存。您不能逐行使用fseek,因为在读取它们之前,您不知道每行有多长。
您应该将整个文件读入行列表中,或者如果文件太大而且您只需要最后几行,则从文件末尾读取固定大小的块,并实现更复杂的逻辑以从这些数据中检测出行。
这段代码可以倒序读取文件。在读取时,该代码会忽略修改,例如处理 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);
逆序逐行读取文件的函数:
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);
这里有一个名为fgetsr()的替代fgets($fp)的函数,可以倒序读取文件中的行。
这段代码是逐字逐句的,所以你应该(最后的话)能够将其复制到服务器上的文件中并运行它。不过你可能需要在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;
}
}
}
?>
我知道这个问题已经有答案了,但我发现另外一种可能更快的方法。
// 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);
}