在Php中读取文件时如何节省内存?

7

我有一个200kb的文件,在多个页面中使用,但每个页面只需要文件中的1-2行,如果我知道行号,如何只读取我需要的这些行呢?

例如,如果我只需要第10行,我不想将所有行加载到内存中,只需加载第10行。

对不起我的英语不好!

7个回答

19
尝试使用SplFileObject
echo memory_get_usage(), PHP_EOL;        // 333200

$file = new SplFileObject('bible.txt');  // 996kb
$file->seek(5000);                       // jump to line 5000 (zero-based)
echo $file->current(), PHP_EOL;          // output current line 

echo memory_get_usage(), PHP_EOL;        // 342984 vs 3319864 when using file()

要输出当前行,你可以使用current()或者只是echo $file。虽然我发现使用方法更清晰。你也可以使用fgets(),但那会获取下一行。
当然,你只需要中间的三行。我添加了memory_get_usage的调用,只是为了证明这种方法几乎不占用内存。

很好。我没有注意到seek是基于行而不是基于字节的。 - Yacoby
我更喜欢这段代码,因为对程序员来说工作量更少,并且对于正在进行的操作(寻找特定行)更清晰,比使用fgets更好。 - davidtbernal
@Yacoby,有SplFileInfo :: fseek()SplFileInfo :: seek()。后者是基于行的,而另一个是基于字节的。 seek()SeekableIterator接口的方法。 - Gordon
3
请注意,这里seek到的行号并不是第5,000行。因为$line_pos参数是从零开始计数的,所以此示例将会寻找在文本编辑器中看到的第5,001行。 - salathe

3
除非您知道该行的偏移量,否则需要逐行阅读每一行。您可以通过使用类似于fgets()的循环遍历文件来丢弃旧行(您不需要的行)。 (编辑:与fgets()不同,我建议使用@Gordon解决方案
可能更好的解决方案是使用数据库,因为数据库引擎将完成存储字符串的繁重工作,并允许您(非常高效地)获取某个“行”(它不会是一行,而是一个带有数字ID的记录,但本质相同),而无需在其之前读取记录。

那个数据库是否更快是主观的。如果他要访问的信息在文件的开头,那么速度会更快。从数据库中读取仍然是从文件中读取。只有当他需要查找远离文件开头的内容时,才能从数据库索引中获得改进。这也取决于他确切想要实现什么。 - Ivo Sabev
2
他从未说过数据库会更快,只是会更好。楼主的担忧可能更多地涉及内存问题而非速度。 - webbiedave
1
正如@webbiedave所说,我从未提到更快。我试图加入建议,即有替代方案可能是解决问题的更好方法,而不是我最初提出的第一个解决方案。 - Yacoby

2

文件内容是否会改变?如果是静态的或相对静态的,您可以建立一个偏移量列表,以便在需要读取数据时使用。例如,如果文件每年更改一次,但您每天读取它数百次,则可以预先计算出所需行的偏移量,并直接跳转到它们,如下所示:

 $offsets = array();
 while ($line = fread($filehandle)) { .... find line 10 .... }
 $offsets[10] = ftell($filehandle); // store line 10's location
 .... find next line
 $offsets[20] = ftell($filehandle);

等等。之后,您可以轻松地跳转到该行的位置,方法如下:

 $fh = fopen('file.txt', 'rb');
 fseek($fh, $offsets[20]); // jump to line 20

但这可能完全是过度的。尝试对操作进行基准测试-比较执行旧式的“读取20行”的时间与预计算/跳转所需的时间。


1
<?php
    $lines = array(1, 2, 10);

    $handle = @fopen("/tmp/inputfile.txt", "r");
    if ($handle) {
        $i = 0;
        while (!feof($handle)) { 
            $line = stream_get_line($handle, 1000000, "\n");

            if (in_array($i, $lines)) {
                echo $line;
                            $line = ''; // Don't forget to clean the buffer!
            }

            if ($i > end($lines)) {
                break;
            }

            $i++;
        } 
        fclose($handle);
    }
?>

0

只需循环遍历它们而无需存储,例如:

$i = 1;
$file = fopen('file.txt', 'r');
while (!feof($file)) {
   $line = fgets($file); // this gets whole line from the file;
   if ($i == 10) {
       break; // break on tenth line
   } 
   $i ++;
}

上面的例子只会保留从文件中获取的最后一行的内存,因此这是最节省内存的方法。

1
  1. 你忘记了 $i++。
  2. 为什么不直接检查 $i == 10?
- zerkms
啊,我总是忘记加上增量。至于那个“== 10”……又是一种习惯性地重复解析太多东西的坏毛病……真的很抱歉,已经修复了 :) - bisko
1
@Ivo:你能测量这个差异吗?顺便说一句,C++代码会比php更快,所以我们需要用C++重写吗? - zerkms
@Ivo,请检查您的硬盘。使用fgets读取10000行需要约0.000327秒,而stream_get_line只需约0.0000532秒。这证实了它更快,但不确定为什么。 - bisko
@brisko 我查看了PHP源代码。fgets()定义在file.c中,而stream_get_line定义在streamsfuncs.c中。你可以阅读它们的源代码,看到fgets()实际上在调用stream_get_line之前进行了一些参数检查和一些结果改进,这使得fgets()稍微慢了一些。这是版本5.3.2。 - Ivo Sabev
显示剩余4条评论

0

使用 fgets()。在这种情况下,您不会将所有10行存储在内存中


0

为什么你只尝试加载前十行?难道你不知道加载所有这些行实际上是一个问题吗?

如果你没有测量过,那么你不知道它是一个问题。不要浪费时间优化非问题。除非你确切地知道加载该文件确实是瓶颈,否则你在不加载整个200K文件时所做的任何性能更改都几乎无法察觉。


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