遍历PHP中字符串中的每一行

Posted

技术标签:

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

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

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

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

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

【问题讨论】:

您可能需要先规范化换行符。 s($myString)->normalizeLineEndings() 方法可用于 github.com/delight-im/PHP-Str(MIT 许可下的库),它有许多其他有用的字符串助手。你可能想看看源代码。 【参考方案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() 会破坏事情。我还用它来抓取字符串中的所有内容,直到第一个空格 (***.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();

SplFileObject 文档:https://www.php.net/manual/en/class.splfileobject.php PHP IO 流:https://www.php.net/manual/en/wrappers.php.php

【讨论】:

以上是关于遍历PHP中字符串中的每一行的主要内容,如果未能解决你的问题,请参考以下文章

在Python中的数据框中的每一行的两个子字符串之间选择字符串

如何使用 Windows 批处理文件遍历文本文件中的每一行?

如何通过遍历行来预测数据框中的每一行?

遍历 CSV 的每一行并使用 Python-Pandas 写入文件夹中的单个 JSON 文件

在 pyspark 的 StructStreaming 中;如何将 DataFrame 中的每一行(json 格式的字符串)转换为多列

array_uintersect 将多维数组的每一行中的特定元素与字符串的平面数组进行比较