在PHP中将一个大字符串拆分成具有相同行数的较小块?

时间:2018-01-31 02:17:23

标签: php string split preg-split

遗留PHP系统将一个巨大的日志文件(~5gb)直接读取到内存中的变量并进行一些处理。

编辑:关于阅读5gb到内存是非常不推荐和其他建议请相信,由于我们无法改变的一些传统设计,这必须保持不变。

现在我需要通过另一项服务处理数据,每次调用最多需要1000行。

我尝试过两种方法,两种方法都有效。

1-将新行char中的整个字符串拆分为一个数组,然后使用array_chunk将该数组拆分为子数组,然后取出每个子数组并使用implode生成一个字符串

$logFileStr; // a variable that already contains 5gb file as string
$logLines = explode(PHP_EOL, $logFileStr);
$lineGroups = array_chunk($logLines, 1000);
foreach($lineGroups as $lineGroup)
{
    $linesChunk = implode(PHP_EOL, $lineGroup);

    $archiveService->store($linesChunk);
}

优点:它很快,因为一切都在记忆中起作用 缺点:涉及大量过度工作和需要大量的记忆

2-最初将字符串变量的内容写入本地临时文件。然后使用exec函数拆分文件

split -l 1000 localfile 

每个产生1000行的大量文件。 然后我可以简单地递归读取文件并将每个文件作为单个字符串处理。

优点:维护更简单,更容易

缺点:磁盘I / O涉及速度慢,写入读取开销很多

我的问题是,因为我已经在内存中有一个包含整个字符串的变量,我怎样才能以可迭代的方式从该变量中读取每行1000行的块,以便我可以避免写入磁盘或生成新数组重新合并开销?

2 个答案:

答案 0 :(得分:0)

解决此问题的一种方法是使用以下步骤:

  1. 将字符串解析为循环中的字符数组。
  2. 计算换行符的数量。
  3. 对于每1000个换行符,提取从前一个子字符串结束处开始并以当前换行符结束的子字符串。
  4. 我创建了一个示例php代码,遵循以上步骤:

    <?php
    $str = "line1\nline2\nline3\nline4\nline5\n"; // Sample string
    $max_new_lines = 2; // Max number of lines. Replace this with 1000
    $str_length = strlen($str);
    $new_line_count = 0;
    $str_chunk = "";
    $start = 0;
    
    // Loop through every character of the string
    for ($i = 0; $i < $str_length; ++$i) {
      if ($str[$i] == "\n") {
        ++$new_line_count;
    
        // If we reached the max number of newlines, extract the substring
        if (($new_line_count % $max_new_lines) == 0) {
          $str_chunk = substr($str, $start, $i - $start);
          $start = $i + 1;
          // echo "\n\nchunk:\n" . $str_chunk;
        }
      }
    }
    
    // Extract the remaining lines
    $str_chunk = substr($str, $start, $i - $start);
    // echo "\n\nchunk:\n" . $str_chunk;
    

答案 1 :(得分:0)

在进行了一些更多的研究后,我偶然发现了这个问题php explode every third instance of character,在对那里发布的答案进行了一些修改后(https://stackoverflow.com/a/1275110/7260022),我想出了这个片段,暂时比以前的方法效果更好

$logFileStr; // a variable that already contains 5gb file as string

$chunks = preg_split('/((?:[^\n]*\n){1000})/', $logFileStr, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);

print_r($chunks);

在测试字符串上,结果如下所示(拆分为3)

Array
(
 [0] => 13923
        27846
        311769

 [1] => 831384
        935307
        1039230

 [2] => 1558845
        1662768
        1766691

 [3] => 1870614

)

正则表达式的说明如下

?:匹配而不创建捕获组

[^ \ n]与任何非新行

匹配

*量词 - 在零和无限时间之间匹配,尽可能多次,根据需要回馈(贪婪)

{1000}量词 - 准确匹配1000次

标志PREG_SPLIT_DELIM_CAPTURE也会在结果集中添加新行字符。