【问题标题】:Is this the most efficient way to get and remove first line in file?这是获取和删除文件第一行的最有效方法吗?
【发布时间】:2010-03-08 21:02:22
【问题描述】:

我有一个脚本,每次调用它都会获取文件的第一行。已知每一行的长度完全相同(32 个字母数字字符)并以“\r\n”结尾。 获取第一行后,脚本将其删除。

这样做是这样的:

$contents = file_get_contents($file));
$first_line = substr($contents, 0, 32);
file_put_contents($file, substr($contents, 32 + 2)); //+2 because we remove also the \r\n

显然它有效,但我想知道是否有更聪明(或更有效)的方法来做到这一点?

在我的简单解决方案中,我基本上读取和重写整个文件只是为了取出并删除第一行

【问题讨论】:

  • 你可以在内存中提高效率(做一个循环,一次读取一行,一次写出除了第一行),但它看起来很复杂,并且会容易出错。我会做和你一样的。文件从第一个字节开始按顺序存储,这是无法回避的事实。
  • 如果您可以将文件存储为索引,并通过索引执行所有 R/W,也许此操作会更快,因为您只需从索引中删除该行,这样做会是比对完整文件执行此操作便宜。但是,如果文件很小,那么 I/O 成本将低于维护索引的开销。
  • 对于类似问题,我能想到的唯一高度优化的解决方案将涉及文件系统驱动程序中的 IOCTL,它将从文件而不触及其余部分。但这是解决不存在问题的学术练习,绝对不是你所追求的。 :)
  • ###why not 将第 3 行的“32 + 2”替换为“34”。### 如果您反复执行此操作,它可能会帮助几毫秒,或者更多。抱歉,这不能是评论(我没有足够的声誉)。

标签: php file


【解决方案1】:

我昨天想出了这个主意:

function read_and_delete_first_line($filename) {
  $file = file($filename);
  $output = $file[0];
  unset($file[0]);
  file_put_contents($filename, $file);
  return $output;
}

【讨论】:

  • 您仍然读取和重写条目文件,但我承认它好一点。 +1
  • 谢谢,正是我要找的东西
  • 我们所说的规模有多大?
【解决方案2】:

没有比重写文件更有效的方法了。

【讨论】:

    【解决方案3】:

    无需创建第二个临时文件,也无需将整个文件放入内存:

    if ($handle = fopen("file", "c+")) {             // open the file in reading and editing mode
        if (flock($handle, LOCK_EX)) {               // lock the file, so no one can read or edit this file 
            while (($line = fgets($handle, 4096)) !== FALSE) { 
                if (!isset($write_position)) {        // move the line to previous position, except the first line
                    $write_position = 0;
                } else {
                    $read_position = ftell($handle); // get actual line
                    fseek($handle, $write_position); // move to previous position
                    fputs($handle, $line);           // put actual line in previous position
                    fseek($handle, $read_position);  // return to actual position
                    $write_position += strlen($line);    // set write position to the next loop
                }
            }
            fflush($handle);                         // write any pending change to file
            ftruncate($handle, $write_position);     // drop the repeated last line
            flock($handle, LOCK_UN);                 // unlock the file
        }
        fclose($handle);
    }
    

    【讨论】:

    • 能否请您在代码旁边添加一些简短的 cmets 来解释您在做什么?!
    • 这段代码不起作用,它只是覆盖了原地的行。在answer by Marcos Fernandez Ramo 中查看基于此的工作版本
    【解决方案4】:

    这将移动文件的第一行,您不需要像使用“文件”函数那样将整个文件加载到内存中。也许对于小文件来说比使用“文件”要慢一点(也许但我敢打赌不是),但能够毫无问题地管理最大的文件。

    $firstline = false;
    if($handle = fopen($logFile,'c+')){
        if(!flock($handle,LOCK_EX)){fclose($handle);}
        $offset = 0;
        $len = filesize($logFile);
        while(($line = fgets($handle,4096)) !== false){
            if(!$firstline){$firstline = $line;$offset = strlen($firstline);continue;}
            $pos = ftell($handle);
            fseek($handle,$pos-strlen($line)-$offset);
            fputs($handle,$line);
            fseek($handle,$pos);
        }
        fflush($handle);
        ftruncate($handle,($len-$offset));
        flock($handle,LOCK_UN);
        fclose($handle);
    }
    

    【讨论】:

    • 它比answer by Edakos更好吗?
    • 我成功了,因为我无法让 Edakos 解决方案正常工作。不过,在第 3 行中有一个错误的“继续”。
    • 确实,该答案中的代码不起作用。 +1 给你。
    • @MarcosFernandezRamos 为什么不编辑并删除错误的“继续”?而不是使用评论来提及它......
    • @발렌텐 我想是懒惰
    【解决方案5】:

    您可以迭代文件,而不是将它们全部放入内存中

    $handle = fopen("file", "r");
    $first = fgets($handle,2048); #get first line.
    $outfile="temp";
    $o = fopen($outfile,"w");
    while (!feof($handle)) {
        $buffer = fgets($handle,2048);
        fwrite($o,$buffer);
    }
    fclose($handle);
    fclose($o);
    rename($outfile,$file);
    

    【讨论】:

    • +1:我认为这更节省内存,但不是更快。当然,如果文件太大而无法放入内存,它不会崩溃。
    【解决方案6】:

    我通常不建议为此类事情打开 shell,但如果您很少在非常大的文件上执行此操作,则可能需要说明:

    $lines = `wc -l myfile` - 1;
    `tail -n $lines myfile > newfile`;
    

    很简单,不需要将整个文件读入内存。

    我不建议将它用于小文件,或者非常频繁地使用。开销太高了。

    【讨论】:

    • 这无论如何都不是有效的,而且代码也不是可移植的
    • 对于 3 GB 的文件,这将比此处发布的大多数答案高效得多。大多数发布的答案都会因大文件上的内存不足错误而死。你是对的,虽然它不是便携式的。有一组非常具体的情况,在这些情况下这个解决方案是有用的/可以接受的。
    • 如果能在这台上获得一些 cmets 会很高兴,我精通 linux,但仍然遇到这个问题。 ty
    【解决方案7】:

    您可以将位置信息存储到文件本身中。例如,文件的前 8 个字节可以存储一个整数。这个整数是文件中第一行的字节偏移量。

    因此,您再也不会删除行了。相反,删除一行意味着改变起始位置。 fseek() 到它,然后正常读取行。

    文件最终会变大。您可以定期清理孤立行以减小文件大小。

    但是说真的,只使用数据库,不要做这样的事情。

    【讨论】:

      【解决方案8】:

      这是一种方法:

      $contents = file($file, FILE_IGNORE_NEW_LINES);
      $first_line = array_shift($contents);
      file_put_contents($file, implode("\r\n", $contents));
      

      还有无数其他方法可以做到这一点,但所有方法都涉及以某种方式分离第一行并保存其余部分。您无法避免重写整个文件。另一种选择:

      list($first_line, $contents) = explode("\r\n", file_get_contents($file), 2);
      file_put_contents($file, implode("\r\n", $contents));
      

      【讨论】:

      • 您的第一个示例将生成多余的换行符。如果没有file()FILE_IGNORE_NEW_LINES 标志,您就不需要再次使用换行符implode()
      • Ulman:+1 非常有趣的代码,谢谢!我以前从未使用过文件功能。
      【解决方案9】:

      我的问题是大文件。我只需要编辑或删除第一行。这是我使用的解决方案。不需要在变量中加载完整的文件。目前回显,但您始终可以保存内容。

      $fh = fopen($local_file, 'rb');
      echo "add\tfirst\tline\n";  // add your new first line.
      fgets($fh); // moves the file pointer to the next line.
      echo stream_get_contents($fh); // flushes the remaining file.
      fclose($fh);
      

      【讨论】:

        【解决方案10】:

        你可以使用 file() 方法。

        获取第一行

        $content = file('myfile.txt');
        echo $content[0];  
        

        【讨论】:

        • 这对我很有用。我不知道为什么有人为此标记你。谢谢。我试图投票给你,但你仍然是 0。:)
        • 这种方法对大文件效率极低。
        • 这不会删除第一行,它只是获取它。
        【解决方案11】:

        我认为这对任何文件大小都是最好的

        $myfile = fopen("yourfile.txt", "r") or die("Unable to open file!");
        $ch=1;
        
        while(!feof($myfile)) {
          $dataline= fgets($myfile) . "<br>";
          if($ch == 2){
          echo str_replace(' ', '&nbsp;', $dataline)."\n";
          }
          $ch = 2;
        } 
        fclose($myfile);
        

        【讨论】:

          【解决方案12】:

          这里的解决方案对我来说效果不佳。 我的解决方案从文件中获取最后一行(不是第一行,在我的情况下,获取第一行或最后一行无关紧要)并将其从该文件中删除。 即使文件非常大(>150000000 行),这也很快。

          function file_pop($file)
          {
              if ($fp = @fopen($file, "c+")) {
                  if (!flock($fp, LOCK_EX)) {
                      fclose($fp);
                  }
                  $pos = -1;
                  $found = 0;
                  while ($found < 2) {
                      if (fseek($fp, $pos--, SEEK_END) < 0) { // can not seek to position
                          rewind($fp); // rewind to the beginnung of the file
                          break;
                      };
                      if (ord(fgetc($fp)) == 10) { // newline
                          $found++;
                      }
                  }
                  $lastpos = ftell($fp); // get current position of file
                  $lastline = fgets($fp); // get current line
          
                  ftruncate($fp, $lastpos); // truncate file to last position
                  flock($fp, LOCK_UN); // unlock
                  fclose($fp); // close the file
          
                  return trim($lastline);
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-10-06
            相关资源
            最近更新 更多