In Sprachen wie Java und C# sind Strings unveränderlich und es kann rechenintensiv sein, einen String zeichenweise zu erstellen. In diesen Sprachen gibt es Bibliotheksklassen, um diese Kosten zu reduzieren, wie z. B. C# System.Text.StringBuilder und Java java.lang.StringBuilder.
Teilt PHP (4 oder 5; ich interessiere mich für beide) diese Einschränkung? Wenn ja, gibt es ähnliche Lösungen für das Problem?
Nein, es gibt keine Art von Stringbuilder-Klasse in PHP, da Strings änderbar sind.
Abgesehen davon gibt es verschiedene Möglichkeiten, eine Saite aufzubauen, je nachdem, was Sie tun.
echo akzeptiert beispielsweise durch Kommas getrennte Tokens für die Ausgabe.
// This...
echo 'one', 'two';
// Is the same as this
echo 'one';
echo 'two';
Das bedeutet, dass Sie eine komplexe Zeichenfolge ausgeben können, ohne tatsächlich eine Verkettung zu verwenden, die langsamer wäre
// This...
echo 'one', 'two';
// Is faster than this...
echo 'one' . 'two';
Wenn Sie diese Ausgabe in einer Variablen erfassen müssen, können Sie dies mit dem tun Ausgangspufferfunktionen.
Außerdem ist die Array-Leistung von PHP wirklich gut. Wenn Sie so etwas wie eine durch Kommas getrennte Liste von Werten erstellen möchten, verwenden Sie einfach implode()
Stellen Sie sicher, dass Sie sich schließlich damit vertraut machen Der String-Typ von PHP und es sind verschiedene Trennzeichen und die Auswirkungen der einzelnen.
Und verwenden Sie nach Möglichkeit einfache Anführungszeichen.
– Stefan
29. Dezember 2010 um 23:15 Uhr
warum keine doppelten Anführungszeichen?
– Tebe
5. Oktober 2013 um 15:52 Uhr
@gekannt Weil PHP Variablen sowie zusätzliche Escape-Sequenzen in Strings erweitert/interpretiert, die in doppelte Anführungszeichen eingeschlossen sind. Zum Beispiel, $x = 5; echo "x = $x"; drucken würde x = 5 während $x = 5; echo 'x = $x'; drucken würde x = $x.
– samitny
10. Oktober 2013 um 19:37 Uhr
man kann es erweitern oder nicht erweitern/interpretieren, es hängt von der Situation ab
… und bekam folgende Ergebnisse. Bild anbei. Sprintf ist eindeutig der am wenigsten effiziente Weg, sowohl in Bezug auf die Zeit als auch auf den Speicherverbrauch. BEARBEITEN: Bild in einem anderen Tab anzeigen, es sei denn, Sie haben Adlerblick.
sollte noch 1 Test haben: ähnlich test2 aber ersetzen . mit , (natürlich ohne Ausgangspuffer)
– Raubvogel
21. November 2013 um 7:26 Uhr
Sehr nützlich, danke. String-Verkettung scheint der richtige Weg zu sein. Es macht Sinn, dass sie versuchen würden, die Hölle daraus zu optimieren.
– Chris Middleton
18. September 2014 um 19:03 Uhr
StringBuilder analog wird in PHP nicht benötigt.
Ich habe ein paar einfache Tests gemacht:
bei 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();
in 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();
Ergebnisse:
10000 Iterationen:
1) PHP, gewöhnliche Verkettung: ~6ms
2) PHP mit StringBuilder: ~5 ms
3) C#, gewöhnliche Verkettung: ~520 ms
4) C#, mit StringBuilder: ~1ms
100000 Iterationen:
1) PHP, gewöhnliche Verkettung: ~63ms
2) PHP mit StringBuilder: ~555 ms
3) C#, gewöhnliche Verkettung: ~91000ms // !!!
4) C#, mit StringBuilder: ~17ms
Java ist in dieser Hinsicht mehr oder weniger dasselbe wie C#. Obwohl die späteren Versionen zur Kompilierzeit einige Optimierungen vorgenommen haben, um dies zu verringern. Früher war es so (in 1.4 und früher, vielleicht sogar in 1.6), dass Sie, wenn Sie 3 oder mehr Elemente zu verketten haben, besser dran waren, einen StringBuffer/Builder zu verwenden. Obwohl in einer Schleife, müssen Sie immer noch den StringBuilder verwenden.
– A.Grandt
11. Januar 2014 um 9:04 Uhr
Mit anderen Worten, PHP wurde für Leute entwickelt, die sich keine Gedanken über Low-Level-Überlegungen machen wollen, und es führt eine interne Zeichenfolgenpufferung für den Zeichenfolgentyp durch. Das hat nichts damit zu tun, dass Strings in PHP “änderbar” sind; Das Verlängern einer Zeichenfolge erfordert immer noch eine Speicherkopie in einen größeren Speicherbereich, es sei denn, Sie unterhalten einen Puffer, in den sie hineinwachsen kann.
– Thomas Rutter
24. März 2017 um 22:25 Uhr
Übrigens sollte dies die akzeptierte Antwort sein. Die aktuellen Top-Antworten beantworten die Frage nicht einmal wirklich.
– Thomas Rutter
24. März 2017 um 22:30 Uhr
Wenn Sie einen Zeitvergleich durchführen, sind die Unterschiede so gering, dass es nicht sehr relevant ist. Es würde mehr bringen, sich für die Wahl zu entscheiden, die Ihren Code leichter lesbar und verständlich macht.
ossis
Ich weiß, wovon du sprichst. Ich habe gerade diese einfache Klasse erstellt, um die Java StringBuilder-Klasse zu emulieren.
class StringBuilder {
private $str = array();
public function __construct() { }
public function append($str) {
$this->str[] = $str;
}
public function toString() {
return implode($this->str);
}
}
Schöne Lösung. Am Ende von append Funktion, die Sie hinzufügen können return $this; Methodenverkettung zulassen: $sb->append("one")->append("two");.
– Jabba
3. Dezember 2010 um 21:20 Uhr
Dies ist in PHP völlig unnötig. Tatsächlich bin ich bereit zu wetten, dass dies erheblich langsamer ist als eine normale Verkettung.
– ryeguy
27. April 2011 um 14:55 Uhr
ryeguy: stimmt, da Strings in PHP veränderbar sind, ist diese Methode “unnötig”, die Person bat um eine ähnliche Implementierung wie Javas StringBuilder, also los geht’s … Ich würde nicht sagen, dass es “deutlich” langsamer ist, denke ich dir sind ein wenig dramatisch. Der Aufwand für die Instanziierung einer Klasse, die die Zeichenfolgenerstellung verwaltet, kann Kosten beinhalten, aber die Nützlichkeit der StringBuilder-Klasse kann erweitert werden, um zusätzliche Methoden für die Zeichenfolge einzubeziehen. Ich werde untersuchen, welcher zusätzliche Overhead realisiert wird, wenn so etwas in einer Klasse implementiert wird, und versuchen, zurück zu posten.
– ossis
11. Mai 2011 um 3:22 Uhr
… und er wurde nie wieder gehört.
– Nigralbus
7. November 2013 um 15:59 Uhr
PHP-Strings sind änderbar. Sie können bestimmte Zeichen wie folgt ändern:
$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)
Und Sie können Zeichen wie folgt an eine Zeichenfolge anhängen:
$string .= 'a';
Schöne Lösung. Am Ende von append Funktion, die Sie hinzufügen können return $this; Methodenverkettung zulassen: $sb->append("one")->append("two");.
– Jabba
3. Dezember 2010 um 21:20 Uhr
Dies ist in PHP völlig unnötig. Tatsächlich bin ich bereit zu wetten, dass dies erheblich langsamer ist als eine normale Verkettung.
– ryeguy
27. April 2011 um 14:55 Uhr
ryeguy: stimmt, da Strings in PHP veränderbar sind, ist diese Methode “unnötig”, die Person bat um eine ähnliche Implementierung wie Javas StringBuilder, also los geht’s … Ich würde nicht sagen, dass es “deutlich” langsamer ist, denke ich dir sind ein wenig dramatisch. Der Aufwand für die Instanziierung einer Klasse, die die Zeichenfolgenerstellung verwaltet, kann Kosten beinhalten, aber die Nützlichkeit der StringBuilder-Klasse kann erweitert werden, um zusätzliche Methoden für die Zeichenfolge einzubeziehen. Ich werde untersuchen, welcher zusätzliche Overhead realisiert wird, wenn so etwas in einer Klasse implementiert wird, und versuchen, zurück zu posten.
– ossis
11. Mai 2011 um 3:22 Uhr
… und er wurde nie wieder gehört.
– Nigralbus
7. November 2013 um 15:59 Uhr
Dakusan
Ich habe den Code am Ende dieses Beitrags geschrieben, um die verschiedenen Formen der Zeichenfolgenverkettung zu testen, und sie sind wirklich alle fast genau gleich in Bezug auf Speicher und Zeitbedarf.
Die beiden primären Methoden, die ich verwendet habe, sind das Verketten von Strings miteinander und das Füllen eines Arrays mit Strings und das anschließende Implodieren. Ich habe 500 String-Ergänzungen mit einem 1-MB-String in PHP 5.6 durchgeführt (das Ergebnis ist also ein 500-MB-String). Bei jeder Iteration des Tests waren alle Speicher- und Zeitabdrücke sehr, sehr eng (bei ~$IterationNumber*1MB). Die Laufzeit beider Tests betrug 50,398 Sekunden und 50,843 Sekunden hintereinander, was höchstwahrscheinlich innerhalb akzeptabler Fehlergrenzen liegt.
Die Garbage-Collection von Zeichenfolgen, auf die nicht mehr verwiesen wird, scheint ziemlich unmittelbar zu sein, auch ohne jemals den Bereich zu verlassen. Da die Strings änderbar sind, wird im Nachhinein kein zusätzlicher Speicher benötigt.
ABERDie folgenden Tests haben gezeigt, dass es einen Unterschied in der Spitzenspeicherauslastung gibt WÄHREND Die Strings werden verkettet.
Ergebnis = 6.613.320 Bytes (~ 6 MB ohne den anfänglichen PHP-Speicherbedarf)
Es gibt also tatsächlich einen Unterschied, der bei sehr, sehr großen Zeichenfolgenverkettungen speichermäßig von Bedeutung sein könnte (ich bin auf solche Beispiele gestoßen, als ich sehr große Datensätze oder SQL-Abfragen erstellte).
Aber auch diese Tatsache ist aufgrund der Daten umstritten. Beispielsweise benötigte das Verketten von 1 Zeichen mit einer Zeichenfolge, um 50 Millionen Bytes (also 50 Millionen Iterationen) zu erhalten, eine maximale Menge von 50.322.512 Bytes (~48 MB) in 5,97 Sekunden. Während der Ausführung der Array-Methode wurden am Ende 7.337.107.176 Bytes (~6,8 GB) verwendet, um das Array in 12,1 Sekunden zu erstellen, und es dauerte dann zusätzliche 4,32 Sekunden, um die Zeichenfolgen aus dem Array zu kombinieren.
Wie auch immer … das Folgende ist der Benchmark-Code, den ich zu Beginn erwähnt habe, der zeigt, dass die Methoden ziemlich gleich sind. Es gibt eine hübsche HTML-Tabelle aus.
<?
//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>';
}
?>
Danke dafür. array_push war 100x schneller als das Verketten von Strings in meinem Code.