遍历PHP字符串中的每一行


130

我有一个允许用户上载文本文件或将文件内容复制/粘贴到文本区域的表格。我可以轻松地区分两者,然后将它们中的任何一个放入字符串变量中,但是我从那里去哪里呢?

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

我是一个非常优秀的程序员,所以我知道有关该方法的一般想法,但是自从使用PHP以来已经很久了,以至于我一直在寻找错误的东西,因此想出了无用的信息。我遇到的关键问题是我想逐行读取字符串的内容。如果是文件,这将很容易。

我主要是在寻找有用的PHP函数,而不是一种算法。有什么建议?


您可能需要先规范换行符。该方法s($myString)->normalizeLineEndings()可在github.com/delight-im/PHP-Str(MIT许可下的库)中使用,该库还有许多其他有用的字符串帮助器。您可能需要看一下源代码。
caw

Answers:


189

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

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

除了\ n \ r之外,它还会处理^ M吗?
Topher Fangio

我不确定将ascii回车符放入变量后是否转换为\ r。如果不是,则可以始终使用带有ascii值的split()/ exlope()代替-ch(13)
Kyril

12
更好的正则表达式是/((\r?\n)|(\r\n?))/
费利克斯Saparelli

3
要匹配Unix LF(\ n),MacOS <9 CR(\ r),Windows CR + LF(\ r \ n)和罕见的LF + CR(\ n \ r),应为:/((\r?\n)|(\n?\r))/
等待开发...

2
对于多字节数据,这可能会造成灾难性的爆炸。
pguardiario

157

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

$separator = "\r\n";
$line = strtok($subject, $separator);

while ($line !== false) {
    # do something with $line
    $line = strtok( $separator );
}

测试性能时,我在具有preg_split17,000 行的测试文件中进行了100次迭代:花了27.7秒,而strtok花了1.4秒。

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

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


21
在处理大型线路时,出于性能考虑+ 1
CodeAngry

4
尽管此函数api完全混乱(使用不同的参数调用),但这是最佳解决方案。既不prey_split也不explode应被用于产生结构化字符串片段。就像瞄准带有火箭筒的苍蝇
Maciej Sz 2014年

1
如果您在应用程序运行时检查内存使用情况,那么您会发现神奇之处。实际上,当您在每一行中循环时,它会将您正在读取的文件拉入内存,保留了令牌位置。您将需要刷新它以真正实现内存高效。php.net/strtok#103051
AbsoluteƵERØ

2
快速说明,strtok()在该while循环内使用其他东西会破坏事情。我还用它来捕获字符串中的所有内容,直到第一个空格(stackoverflow.com/a/2477411/1767412),花了我一分钟才意识到为什么事情没有按计划进行
Billynoah

1
应该是公认的答案,可能是所有选项中最快的解决方案。
约翰

94

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

$lines = explode(PHP_EOL, $subject);

30
当心:它将在不同的系统上工作但不能与来自不同系统的字符串一起工作。在PHP手册规定,PHP_EOL (string)正确的“高端路线”符号这个平台。
wadim

@wadim是正确的!如果您正在Unix服务器上处理Windows文本文件,它将失败。
javsmo 2014年

1
请注意,根据行的长度,这可能会占用大字符串的大量内存。
同步

请注意,如果最后一行包含行终止符,则此后还将返回另一个空字符串。
rightfold

20

它过于复杂和丑陋,但我认为这是可行的方法:

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

1
+1,也可以php://temp用于将较大的数据存储到临时磁盘文件中。
CodeAngry

4
应该注意的是,这与strtok()解决方案不同,它允许您检测空行。该文档是在php.net/manual/en/...
约瑟普·罗丹

7
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:)


6

潜在的内存问题 strtok

由于建议的解决方案之一使用strtok,因此不幸的是,它没有指出潜在的内存问题(尽管它声称具有内存效率)。当使用strtok根据本手册中,:

请注意,只有第一次调用strtok时才使用string参数。随后的对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('', '');
}

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

根据手册,对于文件上传部分,您可以使用以下file命令:

 //Create the array
 $lines = file( $some_file );

 foreach ( $lines as $line ) {
   //do something here.
 }

4

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

“我主要是在寻找有用的PHP函数,而不是一种算法来实现它。有什么建议吗?”

我经常使用这些:

  • 给出单个定界符,explode()可用于将字符串拆分为数组。
  • implode()是explode的对应对象,可从数组返回字符串。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.