如何使用 fseek 逐行向后读取文件?
代码可能会有所帮助。必须是跨平台且纯php。
提前非常感谢
问候
杰拉
问题是使用 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++;
}
如果您要以任何方式读取整个文件,只需使用 file() 将文件作为数组读取(每一行是数组中的每个元素),然后使用 array_reverse() 翻转数组向后并循环。或者只是做一个反向 for 循环,从末尾开始并在每个循环上递减。
$file = file("test.txt");
$file = array_reverse($file);
foreach($file as $f){
echo $f."<br />";
}
<?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--) { $输出.= 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不配合,所以我使用了“ " 来表示行尾。因此,上述方法可能不适用于所有情况。
你不能逐行查找,因为在你阅读它们之前你不知道这些行有多长。
您应该将整个文件读入行列表,或者如果文件太大而您只需要最后几行,则从文件末尾读取固定大小的块并实现一些更复杂的逻辑从此类数据中检测线条。
将整个文件读入数组并反转是可以的,除非文件很大。
您可以使用如下方式从后到前执行文件的缓冲读取:
此代码向后读取文件。此代码忽略读取时的修改,例如 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);
这里有一个名为 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;
}
}
}
?>
反向逐行读取文件的函数:
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);
这是使用缓冲区和产量的简短版本。
<?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";
}
我知道这个问题已经得到解答,但我找到了另一种可能更快的方法。
// 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);
}