【问题标题】:How to save memory when reading a file in Php?在 PHP 中读取文件时如何节省内存?
【发布时间】:2010-04-08 22:13:32
【问题描述】:

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

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

对不起,我的英语不好!

【问题讨论】:

    标签: php file memory line


    【解决方案1】:

    试试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 是基于行而不是基于字节的。
    • +1 我更喜欢这段代码,因为它对程序员来说工作量更少,而且比fgets 更清楚正在发生的事情(寻找特定行)。
    • @Yacoby 有SplFileInfo::fseek()SplFileInfo::seek()。后者是基于行的,另一个是基于字节的。 seek() 是来自SeekableIterator 接口的方法。
    • 请注意,seek-ed 到的行号不是第 5,000 行。 $line_pos 参数从零开始,因此该示例寻求第 5,001 行,就像在文本编辑器等中看到的那样。
    • 谢谢,这真的很有帮助!
    【解决方案2】:

    除非您知道该行的偏移量,否则您需要读取该点之前的每一行。您可以通过使用fgets() 之类的内容循环文件来丢弃旧行(您不想要的行)。 (编辑:而不是fgets(),我建议@Gordon's solution

    可能更好的解决方案是使用数据库,因为数据库引擎将完成存储字符串的繁重工作并允许您(非常有效地)获得某个“行”(它不会是一行,而是具有数字 ID 的记录,但它相当于同一件事),而无需读取它之前的记录。

    【讨论】:

    • 数据库会更快是主观的。如果他试图访问的信息位于文件的开头,则速度会快得多。从数据库读取仍然是从文件读取。只有当他正在寻找远离文件开头的东西时,他才会从数据库索引中获得改进。这也取决于他想要达到的目标。
    • 他从未说过数据库会更快。只有这样会更好。 OP 的担忧可能被视为内存问题而不是速度问题。
    • @Ivo 正如@webbiedave 所说,我从未提到过更快。我试图在建议中添加一些替代方案,可能比我建议的第一个解决方案更好。
    【解决方案3】:

    文件的内容会改变吗?如果它是静态的或相对静态的,您可以在要读取数据的位置构建一个偏移列表。例如,如果文件每年更改一次,但您每天阅读它数百次,那么您可以预先计算您想要的行的偏移​​量并直接跳转到它们,如下所示:

     $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 行”与预计算/跳转需要多长时间。

    【讨论】:

      【解决方案4】:
      <?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);
          }
      ?>
      

      【讨论】:

        【解决方案5】:

        只循环它们而不存储,例如

        $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.你忘了 $i++,2. 为什么不检查 $i == 10 吗?
        • Bleh,我总是忘记输入增量。至于== 10 ...再次,一个坏习惯是重复解析太多东西..真的很抱歉,已修复:)
        • stream_get_line() 比 fgets() 快
        • @Ivo:你能衡量一下这种差异吗?顺便说一句,C++ 代码会比 php 更快——所以我们需要用 C++ 重写?
        • 10,000 行文件 fgets() - 27 秒,stream_get_line() - 0.5 秒。如果需要,您可以使用汇编程序。
        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

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

          如果您没有测量,那么您不知道这是一个问题。不要浪费时间优化非问题。除非您知道加载该文件确实是一个瓶颈,否则您在不加载整个 200K 文件时所发生的任何性能变化都可能是难以察觉的。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-04-03
            • 2021-12-04
            • 1970-01-01
            • 1970-01-01
            • 2019-02-12
            • 2021-12-16
            • 2019-02-05
            • 2020-06-16
            相关资源
            最近更新 更多