Domanda

In linguaggi come Java e C #, le stringhe sono immutabili e può essere computazionalmente costoso costruire una stringa un carattere alla volta. In tali lingue, ci sono classi di librerie per ridurre questo costo come C # System.Text.StringBuilder e Java java.lang.StringBuilder .

PHP (4 o 5; sono interessato a entrambi) condivide questa limitazione? In tal caso, sono disponibili soluzioni simili al problema?

È stato utile?

Soluzione

No, non esiste alcun tipo di classe stringbuilder in PHP, poiché le stringhe sono mutabili.

Detto questo, ci sono diversi modi per costruire una stringa, a seconda di cosa stai facendo.

echo, ad esempio, accetterà i token separati da virgola per l'output.

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

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

Ciò significa che è possibile generare una stringa complessa senza effettivamente utilizzare la concatenazione, che sarebbe più lenta

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

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

Se devi acquisire questo output in una variabile, puoi farlo con le funzioni di buffering dell'output .

Inoltre, le prestazioni dell'array di PHP sono davvero buone. Se vuoi fare qualcosa come un elenco di valori separato da virgole, usa solo implode ()

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

Infine, assicurati di familiarizzare con il tipo di stringa di PHP e i suoi diversi delimitatori e il implicazioni di ciascuno.

Altri suggerimenti

Ero curioso di questo, quindi ho eseguito un test. Ho usato il seguente codice:

<?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);

... E ottenuto i seguenti risultati. Immagine allegata Chiaramente, sprintf è il modo meno efficiente per farlo, sia in termini di tempo che di consumo di memoria. MODIFICA: visualizza l'immagine in un'altra scheda a meno che tu non abbia una visione d'aquila. inserisci qui la descrizione dell'immagine

Quando fai un confronto a tempo, le differenze sono così piccole che non è molto rilevante. Farebbe di più da quando scegliere la scelta che semplifica la lettura e la comprensione del codice.

L'analogo StringBuilder non è necessario in PHP.

Ho fatto un paio di semplici test:

in 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();

Risultati:

10000 iterazioni:
1) PHP, concatenazione ordinaria: ~ 6ms
2) PHP, usando StringBuilder: ~ 5 ms
3) C #, concatenazione ordinaria: ~ 520ms
4) C #, usando StringBuilder: ~ 1ms

100000 iterazioni:
1) PHP, concatenazione ordinaria: ~ 63ms
2) PHP, usando StringBuilder: ~ 555ms
3) C #, concatenazione ordinaria: ~ 91000ms // !!!
4) C #, usando StringBuilder: ~ 17ms

So di cosa stai parlando. Ho appena creato questa semplice classe per emulare la classe StringBuilder Java.

class StringBuilder {

  private $str = array();

  public function __construct() { }

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

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

}

Le stringhe PHP sono modificabili. Puoi modificare caratteri specifici in questo modo:

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

E puoi aggiungere caratteri a una stringa come questa:

$string .= 'a';

Sì. Loro fanno. Ad esempio, se vuoi fare l'eco di un paio di stringhe insieme, usa

echo str1,str2,str3 

anziché

echo str1.str2.str3 
per renderlo un po 'più veloce.

Ho scritto il codice alla fine di questo post per testare le diverse forme di concatenazione di stringhe e sono davvero quasi tutte uguali sia nella memoria che nel footprint.

I due metodi principali che ho usato sono concatenare le stringhe l'una sull'altra, riempire un array di stringhe e quindi imploderle. Ho fatto 500 aggiunte di stringa con una stringa da 1 MB in php 5.6 (quindi il risultato è una stringa da 500 MB). Ad ogni iterazione del test, tutte le impronte di memoria e tempo erano molto vicine (a ~ $ IterationNumber * 1MB). Il tempo di esecuzione di entrambi i test è stato di 50.398 secondi e 50.843 secondi consecutivi, il che è molto probabile con margini di errore accettabili.

La garbage collection di stringhe a cui non si fa più riferimento sembra essere abbastanza immediata, anche senza mai uscire dall'ambito. Poiché le stringhe sono modificabili, non è necessaria alcuna memoria aggiuntiva dopo il fatto.

TUTTAVIA , i seguenti test hanno dimostrato che esiste un diverso utilizzo di memoria di picco MENTRE le stringhe vengono concatenate.

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

Risultato = 10.806.800 byte (~ 10 MB senza il footprint di memoria PHP iniziale)

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

Risultato = 6.613.320 byte (~ 6 MB senza il footprint di memoria PHP iniziale)

Quindi c'è in effetti una differenza che potrebbe essere significativa in concatenazioni di stringhe molto grandi in termini di memoria (ho incontrato esempi simili durante la creazione di set di dati molto grandi o query SQL).

Ma anche questo fatto è discutibile a seconda dei dati. Ad esempio, concatenare 1 carattere su una stringa per ottenere 50 milioni di byte (quindi 50 milioni di iterazioni) ha richiesto un massimo di 50.322.512 byte (~ 48 MB) in 5,97 secondi. Durante l'esecuzione del metodo array è stato utilizzato 7.337.107.176 byte (~ 6,8 GB) per creare l'array in 12,1 secondi, quindi sono stati necessari altri 4,32 secondi per combinare le stringhe dell'array.

Anywho ... il seguito è il codice di riferimento che ho citato all'inizio che mostra che i metodi sono praticamente uguali. Produce una bella tabella 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>';
}
?>

In primo luogo, se non hai bisogno di concatenare le stringhe, non farlo: sarà sempre più veloce

echo $a,$b,$c;

di

echo $a . $b . $c;

Comunque, almeno in PHP5, la concatenazione di stringhe è davvero piuttosto veloce, specialmente se c'è solo un riferimento a una data stringa. Immagino che l'interprete usi internamente una tecnica simile a StringBuilder .

Se stai posizionando i valori delle variabili nelle stringhe PHP, capisco che è leggermente più veloce usare l'inclusione delle variabili in linea (non è il suo nome ufficiale, non ricordo cosa sia)

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

Deve essere racchiuso tra virgolette doppie per funzionare. Funziona anche con i membri dell'array (ad es.

echo "You requested page id {

Se stai posizionando i valori delle variabili nelle stringhe PHP, capisco che è leggermente più veloce usare l'inclusione delle variabili in linea (non è il suo nome ufficiale, non ricordo cosa sia)

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

Deve essere racchiuso tra virgolette doppie per funzionare. Funziona anche con i membri dell'array (ad es.

<*>

)

POST['id']}";

)

nessuna tale limitazione in php, php può concatenare la stringa con l'operatore punto (.)

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

genera " ciao mondo "

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top