【问题标题】:Iterate over each line in a string in PHP遍历PHP中字符串中的每一行
【发布时间】:2010-11-30 13:38:00
【问题描述】:

我有一个表单,允许用户上传文本文件或将文件内容复制/粘贴到文本区域。我可以轻松区分这两者并将他们输入的任何一个放入字符串变量中,但我该从哪里开始呢?

我需要遍历字符串的每一行(最好不要担心不同机器上的换行符),确保它只有一个标记(没有空格、制表符、逗号等),清理数据,然后生成基于所有行的 SQL 查询。

我是一个相当优秀的程序员,所以我知道如何做到这一点的一般想法,但是自从我使用 PHP 以来已经很长时间了,我觉得我正在寻找错误的东西,因此提出了无用的信息.我遇到的关键问题是我想逐行读取字符串的内容。如果它是一个文件,那就很容易了。

我主要在寻找有用的 PHP 函数,而不是如何做到这一点的算法。有什么建议吗?

【问题讨论】:

  • 您可能需要先规范化换行符。 s($myString)->normalizeLineEndings() 方法可用于 github.com/delight-im/PHP-Str(MIT 许可下的库),它有许多其他有用的字符串助手。你可能想看看源代码。

标签: php string


【解决方案1】:

preg_split包含文本的变量,并遍历返回的数组:

foreach(preg_split("/((\r?\n)|(\r\n?))/", $subject) as $line){
    // do stuff with $line
} 

【讨论】:

  • 除了 \n\r 之外还会处理 ^M 吗?
  • 我不确定 ascii 回车是否会在放入变量后转换为 \r。如果不是,您始终可以使用带有 ascii 值的 split()/exlope() 代替 -- ch(13)
  • 一个更好的正则表达式是/((\r?\n)|(\r\n?))/
  • 为了匹配 Unix LF (\n), MacOS/((\r?\n)|(\n?\r))/
  • 这对于多字节数据可能会造成灾难性的轰炸。
【解决方案2】:

考虑到您需要能够在不同的机器上处理换行符,Kyril 的回答是最好的。

“我主要是在寻找有用的 PHP 函数,而不是一个算法 去做吧。有什么建议吗?”

我经常使用这些:

  • explode() 可用于将字符串拆分为数组,给定一个 单个分隔符。
  • implode() 是explode 的对应物,从数组返回到字符串。

【讨论】:

    【解决方案3】:

    如果您需要在不同系统中处理换行符,您可以简单地使用 PHP 预定义常量 PHP_EOL (http://php.net/manual/en/reserved.constants.php) 并简单地使用 explode 来避免开销正则表达式引擎。

    $lines = explode(PHP_EOL, $subject);
    

    【讨论】:

    • 注意:它可以在不同的系统上工作,但它不能很好地用于来自不同系统的字符串PHP Manual 声明 PHP_EOL (string) 平台的正确“行尾”符号。
    • @wadim 是对的!如果您在 Unix 服务器上处理 Windows 文本文件,它将失败。
    • 请注意,根据行的长度,这可能会为大字符串占用大量内存。
    • 请注意,如果最后一行包含行终止符,那么这之后还会返回另一个空字符串。
    【解决方案4】:

    我想提出一个明显更快(并且内存效率更高)的替代方案:strtok 而不是preg_split

    $separator = "\r\n";
    $line = strtok($subject, $separator);
    
    while ($line !== false) {
        # do something with $line
        $line = strtok( $separator );
    }
    

    为了测试性能,我对一个 17000 行的测试文件迭代了 100 次:preg_split 用了 27.7 秒,而strtok 用了 1.4 秒。

    请注意,尽管$separator 被定义为"\r\n"strtok 将在任一字符上分开 - 从 PHP4.1.0 开始,跳过空行/标记。

    参见 strtok 手册条目: http://php.net/strtok

    【讨论】:

    • +1 用于性能考虑 处理大型行集时。
    • 虽然这个函数api是一团糟(用不同的参数调用)这是最好的解决方案。 prey_splitexplode 都不应该用于生成结构化字符串片段。这就像用火箭筒瞄准苍蝇
    • 如果您在应用程序运行时检查内存使用情况,那么您将看到神奇之处。如果您循环遍历每一行,它实际上会将您正在读取的文件拉入内存,并且它会保留您的令牌位置。您需要刷新它以真正提高内存效率。 php.net/strtok#103051
    • 快速说明,在 while 循环内的其他东西上使用 strtok() 会破坏事情。我还用它来抓取字符串中的所有内容,直到第一个空格 (stackoverflow.com/a/2477411/1767412) 并花了我一分钟才意识到为什么事情没有按计划进行
    • 应该是公认的答案,可能是所有选项中最快的解决方案。
    【解决方案5】:

    它过于复杂和丑陋,但我认为这是要走的路:

    $fp = fopen("php://memory", 'r+');
    fputs($fp, $data);
    rewind($fp);
    while($line = fgets($fp)){
      // deal with $line
    }
    fclose($fp);
    

    【讨论】:

    • +1 也可以使用php://temp 将较大的数据存储到临时磁盘文件中。
    • 应该注意的是,这允许您检测空行,这与 strtok() 解决方案不同。文档位于php.net/manual/en/…
    【解决方案6】:
    foreach(preg_split('~[\r\n]+~', $text) as $line){
        if(empty($line) or ctype_space($line)) continue; // skip only spaces
        // if(!strlen($line = trim($line))) continue; // or trim by force and skip empty
        // $line is trimmed and nice here so use it
    }
    

    ^ 这就是你正确换行的方式,跨平台兼容Regexp :)

    【讨论】:

      【解决方案7】:

      strtok 的潜在内存问题:

      由于建议的解决方案之一使用strtok,不幸的是,它没有指出潜在的内存问题(尽管它声称内存高效)。根据the manual使用strtok时:

      请注意,只有第一次调用 strtok 时才使用字符串参数。 每次对 strtok 的后续调用只需要使用令牌,因为它 跟踪它在当前字符串中的位置。

      它通过将文件加载到内存中来做到这一点。如果您使用的是大文件,则需要在循环完文件后刷新它们。

      <?php
      function process($str) {
          $line = strtok($str, PHP_EOL);
      
          /*do something with the first line here...*/
      
          while ($line !== FALSE) {
              // get the next line
              $line = strtok(PHP_EOL);
      
              /*do something with the rest of the lines here...*/
      
          }
          //the bit that frees up memory
          strtok('', '');
      }
      

      如果您只关心物理文件(例如数据挖掘):

      According to the manual,文件上传部分可以使用file命令:

       //Create the array
       $lines = file( $some_file );
      
       foreach ( $lines as $line ) {
         //do something here.
       }
      

      【讨论】:

        【解决方案8】:

        与@pguardiario 类似,但使用更“现代”(OOP)的界面:

        $fileObject = new \SplFileObject('php://memory', 'r+');
        $fileObject->fwrite($content);
        $fileObject->rewind();
        
        while ($fileObject->valid()) {
            $line = $fileObject->current();
            $fileObject->next();
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-10-14
          • 2011-05-13
          • 1970-01-01
          • 2012-09-12
          • 1970-01-01
          • 2021-01-11
          • 2018-10-19
          相关资源
          最近更新 更多