字符串连接,性能

在 Java 和 C # 这样的语言中,字符串是不可变的,而且一次构建一个字符串可能需要很高的计算成本。在上述语言中,有一些库类可以降低成本,如 C # System.Text.StringBuilder和 Javajava.lang.StringBuilder

Php (4或5; 我对两者都感兴趣)也有这个限制吗?如果有,是否有类似的解决方案?

56121 次浏览

Php 没有这样的限制, Php 可以将 strng 与 dot (.)运算符连接起来

$a="hello ";
$b="world";
echo $a.$b;

输出“ hello world”

例如,如果你想把两个字符串放在一起,使用

echo str1,str2,str3 

而不是

回显 str1.str2.str3 
以使其更快一些。

不,在 PHP 中没有 stringBuilder 类的类型,因为字符串是可变的。

也就是说,构建字符串有不同的方法,这取决于您正在做的事情。

例如,echo 将接受以逗号分隔的标记作为输出。

// This...
echo 'one', 'two';


// Is the same as this
echo 'one';
echo 'two';

这意味着您可以输出一个复杂的字符串,而不必实际使用串联,这样会慢一些

// This...
echo 'one', 'two';


// Is faster than this...
echo 'one' . 'two';

如果需要在变量中捕获这个输出,可以使用 输出缓冲函数输出缓冲函数

而且,PHP 的数组性能非常好。如果希望执行类似逗号分隔的值列表的操作,只需使用 implode ()

$values = array( 'one', 'two', 'three' );
$valueList = implode( ', ', $values );

最后,确保您熟悉 PHP 的字符串类型及其不同的分隔符,以及每种分隔符的含义。

PHP 字符串是可变的。您可以像这样更改特定的字符:

$string = 'abc';
$string[2] = 'a'; // $string equals 'aba'
$string[3] = 'd'; // $string equals 'abad'
$string[5] = 'e'; // $string equals 'abad e' (fills character(s) in between with spaces)

你可以像下面这样给字符串添加字符:

$string .= 'a';

首先,如果你不需要连接字符串,不要这样做: 这样做总是更快

echo $a,$b,$c;

echo $a . $b . $c;

但是,至少在 PHP5中,字符串串联确实非常快,特别是如果只有一个对给定字符串的引用。我猜解释器在内部使用类似于 StringBuilder的技术。

如果在 PHP 字符串中放置变量值,我理解使用行内变量包含会稍微快一些(这不是正式名称——我记不清是什么)

$aString = 'oranges';
$compareString = "comparing apples to {$aString}!";
echo $compareString
comparing apples to oranges!

必须在双引号内才能工作。对于数组成员(即。

echo "You requested page id {$_POST['id']}";

)

当你进行定时比较时,差异是如此之小,以至于它不是很相关。如果选择使代码更容易阅读和理解的方法,那么将会有更多的收获。

我知道你在说什么,我只是创建了这个简单的类来模拟 JavaStringBuilder 类。

class StringBuilder {


private $str = array();


public function __construct() { }


public function append($str) {
$this->str[] = $str;
}


public function toString() {
return implode($this->str);
}


}

在 PHP 中不需要 StringBuilder 模拟。

我做了几个简单的测试:

在 PHP 中:

$iterations = 10000;
$stringToAppend = 'TESTSTR';
$timer = new Timer(); // based on microtime()
$s = '';
for($i = 0; $i < $iterations; $i++)
{
$s .= ($i . $stringToAppend);
}
$timer->VarDumpCurrentTimerValue();


$timer->Restart();


// Used purlogic's implementation.
// I tried other implementations, but they are not faster
$sb = new StringBuilder();


for($i = 0; $i < $iterations; $i++)
{
$sb->append($i);
$sb->append($stringToAppend);
}
$ss = $sb->toString();
$timer->VarDumpCurrentTimerValue();

在 C # (. NET 4.0)中:

const int iterations = 10000;
const string stringToAppend = "TESTSTR";
string s = "";
var timer = new Timer(); // based on StopWatch


for(int i = 0; i < iterations; i++)
{
s += (i + stringToAppend);
}


timer.ShowCurrentTimerValue();


timer.Restart();


var sb = new StringBuilder();


for(int i = 0; i < iterations; i++)
{
sb.Append(i);
sb.Append(stringToAppend);
}


string ss = sb.ToString();


timer.ShowCurrentTimerValue();

结果:

10000次迭代:
1) PHP,普通级联: ~ 6ms
2) PHP,使用 StringBuilder: ~ 5ms
3) C # ,普通级联: ~ 520ms
4) C # ,使用 StringBuilder: ~ 1ms

100000次迭代:
1) PHP,普通级联: ~ 63ms
2) PHP,使用 StringBuilder: ~ 555ms
3) C # ,普通级联: ~ 91000ms//! ! !
4) C # ,使用 StringBuilder: ~ 17ms

我对此很好奇,所以我运行了一个测试:

<?php
ini_set('memory_limit', '1024M');
define ('CORE_PATH', '/Users/foo');
define ('DS', DIRECTORY_SEPARATOR);


$numtests = 1000000;


function test1($numtests)
{
$CORE_PATH = '/Users/foo';
$DS = DIRECTORY_SEPARATOR;
$a = array();


$startmem = memory_get_usage();
$a_start = microtime(true);
for ($i = 0; $i < $numtests; $i++) {
$a[] = sprintf('%s%sDesktop%sjunk.php', $CORE_PATH, $DS, $DS);
}
$a_end = microtime(true);
$a_mem = memory_get_usage();


$timeused = $a_end - $a_start;
$memused = $a_mem - $startmem;


echo "TEST 1: sprintf()\n";
echo "TIME: {$timeused}\nMEMORY: $memused\n\n\n";
}


function test2($numtests)
{
$CORE_PATH = '/Users/shigh';
$DS = DIRECTORY_SEPARATOR;
$a = array();


$startmem = memory_get_usage();
$a_start = microtime(true);
for ($i = 0; $i < $numtests; $i++) {
$a[] = $CORE_PATH . $DS . 'Desktop' . $DS . 'junk.php';
}
$a_end = microtime(true);
$a_mem = memory_get_usage();


$timeused = $a_end - $a_start;
$memused = $a_mem - $startmem;


echo "TEST 2: Concatenation\n";
echo "TIME: {$timeused}\nMEMORY: $memused\n\n\n";
}


function test3($numtests)
{
$CORE_PATH = '/Users/shigh';
$DS = DIRECTORY_SEPARATOR;
$a = array();


$startmem = memory_get_usage();
$a_start = microtime(true);
for ($i = 0; $i < $numtests; $i++) {
ob_start();
echo $CORE_PATH,$DS,'Desktop',$DS,'junk.php';
$aa = ob_get_contents();
ob_end_clean();
$a[] = $aa;
}
$a_end = microtime(true);
$a_mem = memory_get_usage();


$timeused = $a_end - $a_start;
$memused = $a_mem - $startmem;


echo "TEST 3: Buffering Method\n";
echo "TIME: {$timeused}\nMEMORY: $memused\n\n\n";
}


function test4($numtests)
{
$CORE_PATH = '/Users/shigh';
$DS = DIRECTORY_SEPARATOR;
$a = array();


$startmem = memory_get_usage();
$a_start = microtime(true);
for ($i = 0; $i < $numtests; $i++) {
$a[] = "{$CORE_PATH}{$DS}Desktop{$DS}junk.php";
}
$a_end = microtime(true);
$a_mem = memory_get_usage();


$timeused = $a_end - $a_start;
$memused = $a_mem - $startmem;


echo "TEST 4: Braced in-line variables\n";
echo "TIME: {$timeused}\nMEMORY: $memused\n\n\n";
}


function test5($numtests)
{
$a = array();


$startmem = memory_get_usage();
$a_start = microtime(true);
for ($i = 0; $i < $numtests; $i++) {
$CORE_PATH = CORE_PATH;
$DS = DIRECTORY_SEPARATOR;
$a[] = "{$CORE_PATH}{$DS}Desktop{$DS}junk.php";
}
$a_end = microtime(true);
$a_mem = memory_get_usage();


$timeused = $a_end - $a_start;
$memused = $a_mem - $startmem;


echo "TEST 5: Braced inline variables with loop-level assignments\n";
echo "TIME: {$timeused}\nMEMORY: $memused\n\n\n";
}


test1($numtests);
test2($numtests);
test3($numtests);
test4($numtests);
test5($numtests);

结果如下。附图。显然,就时间和内存消耗而言,sprintf 是效率最低的方法。 编辑: 查看图像在另一个标签,除非你有鹰视觉。 enter image description here

我在这篇文章的末尾写了代码来测试不同形式的字符串串联,它们在内存和时间上几乎完全相同。

我使用的两个主要方法是将字符串连接到彼此上,并用字符串填充数组,然后将其内爆。我在 php 5.6中使用1 MB 字符串添加了500个字符串(因此结果是500 MB 字符串)。 在测试的每次迭代中,所有的内存和时间占用都非常接近(在 ~ $IterationNumber * 1MB)。两个测试的运行时分别为50.398秒和50.843秒,这两个时间段很可能在可接受的误差范围内。

不再被引用的字符串的垃圾收集看起来非常直接,甚至不需要离开作用域。因为字符串是可变的,所以事后不需要额外的内存。

然而 ,下面的测试表明,字符串串联时的峰值内存使用 同时有所不同。

$OneMB=str_repeat('x', 1024*1024);
$Final=$OneMB.$OneMB.$OneMB.$OneMB.$OneMB;
print memory_get_peak_usage();

结果 = 10,806,800字节(初始 PHP 内存占用约10MB w/o)

$OneMB=str_repeat('x', 1024*1024);
$Final=implode('', Array($OneMB, $OneMB, $OneMB, $OneMB, $OneMB));
print memory_get_peak_usage();

结果 = 6,613,320字节(初始 PHP 内存占用大约为6MB w/o)

因此,实际上,在非常非常大的字符串串联的内存方面,存在一个显著的差异(我在创建非常大的数据集或 SQL 查询时遇到过这样的例子)。

但是,根据数据,即使这个事实也是有争议的。例如,将1个字符串接到一个字符串上以获得5000万字节(即5000万次迭代) ,需要在5.97秒内最大50,322,512字节(~ 48 MB)。在执行 array 方法时,用了7,337,107,176个字节(~ 6.8 GB)在12.1秒内创建了数组,然后又花了4.32秒来组合数组中的字符串。

无论如何... 下面是我在开头提到的基准代码,它显示了这些方法是非常相等的。它输出一个漂亮的 HTML 表。

<?
//Please note, for the recursion test to go beyond 256, xdebug.max_nesting_level needs to be raised. You also may need to update your memory_limit depending on the number of iterations


//Output the start memory
print 'Start: '.memory_get_usage()."B<br><br>Below test results are in MB<br>";


//Our 1MB string
global $OneMB, $NumIterations;
$OneMB=str_repeat('x', 1024*1024);
$NumIterations=500;


//Run the tests
$ConcatTest=RunTest('ConcatTest');
$ImplodeTest=RunTest('ImplodeTest');
$RecurseTest=RunTest('RecurseTest');


//Output the results in a table
OutputResults(
Array('ConcatTest', 'ImplodeTest', 'RecurseTest'),
Array($ConcatTest, $ImplodeTest, $RecurseTest)
);


//Start a test run by initializing the array that will hold the results and manipulating those results after the test is complete
function RunTest($TestName)
{
$CurrentTestNums=Array();
$TestStartMem=memory_get_usage();
$StartTime=microtime(true);
RunTestReal($TestName, $CurrentTestNums, $StrLen);
$CurrentTestNums[]=memory_get_usage();


//Subtract $TestStartMem from all other numbers
foreach($CurrentTestNums as &$Num)
$Num-=$TestStartMem;
unset($Num);


$CurrentTestNums[]=$StrLen;
$CurrentTestNums[]=microtime(true)-$StartTime;


return $CurrentTestNums;
}


//Initialize the test and store the memory allocated at the end of the test, with the result
function RunTestReal($TestName, &$CurrentTestNums, &$StrLen)
{
$R=$TestName($CurrentTestNums);
$CurrentTestNums[]=memory_get_usage();
$StrLen=strlen($R);
}


//Concatenate 1MB string over and over onto a single string
function ConcatTest(&$CurrentTestNums)
{
global $OneMB, $NumIterations;
$Result='';
for($i=0;$i<$NumIterations;$i++)
{
$Result.=$OneMB;
$CurrentTestNums[]=memory_get_usage();
}
return $Result;
}


//Create an array of 1MB strings and then join w/ an implode
function ImplodeTest(&$CurrentTestNums)
{
global $OneMB, $NumIterations;
$Result=Array();
for($i=0;$i<$NumIterations;$i++)
{
$Result[]=$OneMB;
$CurrentTestNums[]=memory_get_usage();
}
return implode('', $Result);
}


//Recursively add strings onto each other
function RecurseTest(&$CurrentTestNums, $TestNum=0)
{
Global $OneMB, $NumIterations;
if($TestNum==$NumIterations)
return '';


$NewStr=RecurseTest($CurrentTestNums, $TestNum+1).$OneMB;
$CurrentTestNums[]=memory_get_usage();
return $NewStr;
}


//Output the results in a table
function OutputResults($TestNames, $TestResults)
{
global $NumIterations;
print '<table border=1 cellspacing=0 cellpadding=2><tr><th>Test Name</th><th>'.implode('</th><th>', $TestNames).'</th></tr>';
$FinalNames=Array('Final Result', 'Clean');
for($i=0;$i<$NumIterations+2;$i++)
{
$TestName=($i<$NumIterations ? $i : $FinalNames[$i-$NumIterations]);
print "<tr><th>$TestName</th>";
foreach($TestResults as $TR)
printf('<td>%07.4f</td>', $TR[$i]/1024/1024);
print '</tr>';
}


//Other result numbers
print '<tr><th>Final String Size</th>';
foreach($TestResults as $TR)
printf('<td>%d</td>', $TR[$NumIterations+2]);
print '</tr><tr><th>Runtime</th>';
foreach($TestResults as $TR)
printf('<td>%s</td>', $TR[$NumIterations+3]);
print '</tr></table>';
}
?>

我只是碰到了这个问题:

$str. = ‘字符串连接’;

对。

$str = $str.’字符串连接.’;

目前为止,似乎还没有人比较过这个。 结果是相当疯狂的,有50.000次迭代和 php 7.4:

字符串1:0.0013918876647949

字符串2:1.1183910369873

要素: 803! ! !

$currentTime = microtime(true);
$str = '';
for ($i = 50000; $i > 0; $i--) {
$str .= 'String concatenation. ';
}
$currentTime2 = microtime(true);
echo "String 1: " . ( $currentTime2 - $currentTime);


$str = '';
for ($i = 50000; $i > 0; $i--) {
$str = $str . 'String concatenation. ';
}
$currentTime3 = microtime(true);
echo "<br>String 2: " . ($currentTime3 - $currentTime2);


echo "<br><br>Faktor: " . (($currentTime3 - $currentTime2) / ( $currentTime2 - $currentTime));

有人能证明吗?我之所以遇到这种情况,是因为我正在通过读操作从一个大文件中删除一些行,并且只再次将所需的行附加到一个字符串上。

在我得到暂停之前,用。 = 解决了我在这里的所有问题!