如何在PHP中打开从第X行到第Y行的文件?

14

我在 PHP 文档中看到的最接近的方法是使用 fread() 读取指定长度的内容,但它并没有指定从哪一行开始。您有其他建议吗?

7个回答

38

使用 SplFileObject::seek 可以轻松实现该功能。

$file = new SplFileObject('filename.txt');
$file->seek(1000);
for($i = 0; !$file->eof() && $i < 1000; $i++) {
    echo $file->current(); 
    $file->next();
}

这是来自SeekableIterator接口的方法,不要与fseek混淆。

由于SplFileObject是可迭代的,所以您甚至可以使用LimitIterator更轻松地完成它:

$file = new SplFileObject('longFile.txt');
$fileIterator = new LimitIterator($file, 1000, 2000);
foreach($fileIterator as $line) {
    echo $line, PHP_EOL;
}

同样的,这是从零开始计算的,因此它是第1001到第2001行。


2
+1 SPL非常好,可以多做一些宣传(和文档)。 - Matteo Riva
4
请记住,SPL 实现方式与第一个建议的解决方案相同。它将从第一个字节开始逐行读取文件,并将文件指针留在所需行。这个问题无法避免。 - MatsLindh

14

因为行的长度可能是任意的,所以你将无法从第X行开始读取。因此,你需要从开头开始阅读并计算已读取的行数,直到达到第X行。例如:

<?php
$f = fopen('sample.txt', 'r');
$lineNo = 0;
$startLine = 3;
$endLine = 6;
while ($line = fgets($f)) {
    $lineNo++;
    if ($lineNo >= $startLine) {
        echo $line;
    }
    if ($lineNo == $endLine) {
        break;
    }
}
fclose($f);

1
是的,除了读整个文件,这段代码只读取所需的最小部分。 - grom
这个函数在处理非常大的文件时效率极低。如果我想获取一个有100万行的文件的最后100行,循环将会运行100万次。 - paullb
@paullb 是的,但是行没有固定的长度,所以无法避免。但是如果你想要最后100行,你可以从文件末尾开始向后读取。 - grom
另一个使用LimitIterator的答案完美地解决了问题。(这应该是最好的答案。截至2012-07-19,情况并非如此) - paullb
1
如果您在运行时间较长的脚本和较低版本的PHP中遇到内存问题,最好尽可能避免使用对象,这就是为什么我更喜欢@grom的答案。 - im3r3k
显示剩余4条评论

3

好的,您不能使用函数fseek来寻找适当的位置,因为它是按照给定字节数工作的。

我认为,如果没有某种缓存或者一个一个地查找每一行,这是不可能实现的。


4
如何使用行缓存?将每行的字节位置存储在某个地方,然后使用fseek()跳转到它们。 - Christian Studer
@christian studer:您在谈论文件索引吗?如果该文件是静态的且大部分时间不会更改,那么这可能是一个有趣的解决方案。不幸的是,我要读取的文件是仍在开发中的源代码文件,因此索引不可行。 - thedp
1
是的,对其进行索引并缓存索引。(文件的时间戳可能会提示缓存的索引是否仍然有效)。对于这些任务,PHP的速度出奇地快,如果在文件再次更改之前有多个请求访问同一文件,则可能足够快。 - Christian Studer

3

很遗憾,要想从第x行读取到第y行,您需要能够检测到换行符...并且必须扫描整个文件。但是,假设您不是出于性能原因而询问此事,则可以使用以下代码获取第x到第y行:

$x = 10; //inclusive start line
$y = 20; //inclusive end line
$lines = file('myfile.txt');
$my_important_lines = array_slice($lines, $x, $y);

请参阅:array_slice


你应该注意数组从0开始,而行号通常从1开始。因此,在使用$x-1、$y-1时需要记住,或者记住$x=10实际上是$x=11。 - null

2

这里是可能的解决方案 :)


(意思是在上面的链接中提供了一个可能的解决方案)
<?php
$f = fopen('sample.txt', 'r');
$lineNo = 0;
$startLine = 3;
$endLine = 6;
while ($line = fgets($f)) {
    $lineNo++;
    if ($lineNo >= $startLine) {
        echo $line;
    }
    if ($lineNo == $endLine) {
        break;
    }
}
fclose($f);
?>

2
但它会读取 X 之前的所有行。问题是在问是否可以跳过这部分,对吗? - MartyIX
1
这个问题不仅仅是处理给定的行(>=startLine && <= endLine),而是最小化磁盘读取操作的数量。 - MartyIX
这是一个不错的解决方案,但仍然很耗时间。你越深入文件,花费的时间就会越长...而我打算处理有10,000行以上的文件。 - thedp
@thedp:让我们看看是否有更好的解决方案 :) - Sarfraz
这看起来像是从grom的答案复制过来的 - 请分享您所做的更改。 - Nico Haase
显示剩余4条评论

0

我就怕这样...那么只能执行计划B了 :S

对于每个AJAX请求,我将会:

  1. 读取一个字符串,其中包含我将要返回给客户端的行数。
  2. 剩余部分的文件复制到临时文件中。
  3. 将字符串返回给客户端。

这很糟糕,而且在有10,000多行的文件时可能会非常慢,但我想这比一遍又一遍地读取相同的内容要好,至少每次请求后临时文件都会变得更短...不是吗?


0

如果你正在寻找行数,那么你不能使用fread,因为它依赖于字节偏移量,而不是换行符的数量。实际上,你必须读取文件以查找换行符,因此另一个函数更合适。fgets将逐行读取文件。将其放入循环中并仅捕获你想要的行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接