Question

Dans des langages tels que Java et C #, les chaînes sont immuables et il peut être coûteux en calculs de créer une chaîne un caractère à la fois. Dans ces langages, certaines classes de bibliothèque permettent de réduire ce coût, telles que C # System.Text.StringBuilder et Java java.lang.StringBuilder .

Est-ce que php (4 ou 5; les deux m'intéressent) partage cette limitation? Si tel est le cas, existe-t-il des solutions similaires au problème?

Était-ce utile?

La solution

Non, il n'y a pas de type de classe stringbuilder en PHP, car les chaînes sont mutables.

Cela étant dit, il existe différentes manières de construire une chaîne, en fonction de ce que vous faites.

echo, par exemple, acceptera les jetons séparés par des virgules.

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

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

Cela signifie que vous pouvez générer une chaîne complexe sans utiliser la concaténation, ce qui serait plus lent

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

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

Si vous devez capturer cette sortie dans une variable, vous pouvez le faire avec les fonctions de mise en mémoire tampon de la sortie . .

De plus, les performances de votre tableau en PHP sont vraiment bonnes. Si vous voulez faire quelque chose comme une liste de valeurs séparées par des virgules, utilisez simplement implode ()

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

Enfin, assurez-vous de vous familiariser avec le type de chaîne PHP et ses différents délimiteurs, ainsi que le implications de chacun.

Autres conseils

J'étais curieux à ce sujet, alors j'ai fait un test. J'ai utilisé le code suivant:

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

... Et obtenu les résultats suivants. Image jointe. De toute évidence, sprintf est le moyen le moins efficace, à la fois en termes de temps et de consommation de mémoire. EDIT: affiche l'image dans un autre onglet sauf si vous avez une vision d'aigle. entrer la description de l'image ici

Lorsque vous effectuez une comparaison chronométrée, les différences sont si minimes qu’elles ne sont pas très pertinentes. Cela ferait plus puisque choisir pour rendre votre code plus facile à lire et à comprendre.

L'analogue StringBuilder n'est pas nécessaire en PHP.

J'ai fait quelques tests simples:

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

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

Résultats:

10000 itérations:
1) PHP, concaténation ordinaire: ~ 6 ms
2) PHP, en utilisant StringBuilder: ~ 5 ms
3) C #, concaténation ordinaire: ~ 520 ms
4) C #, en utilisant StringBuilder: ~ 1 ms

100000 itérations:
1) PHP, concaténation ordinaire: ~ 63 ms
2) PHP, en utilisant StringBuilder: ~ 555ms
3) C #, concaténation ordinaire: ~ 91000ms // !!!
4) C #, en utilisant StringBuilder: ~ 17ms

Je sais de quoi tu parles. Je viens de créer cette classe simple pour émuler la classe Java StringBuilder.

class StringBuilder {

  private $str = array();

  public function __construct() { }

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

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

}

Les chaînes PHP sont mutables. Vous pouvez modifier des caractères spécifiques comme ceci:

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

Et vous pouvez ajouter des caractères à une chaîne comme celle-ci:

$string .= 'a';

Oui. Ils font. Par exemple, si vous souhaitez faire écho à plusieurs chaînes ensemble, utilisez

echo str1,str2,str3 

au lieu de

echo str1.str2.str3 
pour aller un peu plus vite.

J'ai écrit le code à la fin de cet article pour tester les différentes formes de concaténation de chaînes. Elles sont toutes presque identiques, à la fois en mémoire et en temps.

Les deux méthodes principales que j'ai utilisées sont la concaténation de chaînes, le remplissage d'un tableau avec des chaînes, puis leur implosion. J'ai fait 500 additions de chaîne avec une chaîne de 1 Mo en PHP 5.6 (donc le résultat est une chaîne de 500 Mo). À chaque itération du test, toutes les empreintes de mémoire et de temps étaient très proches (à ~ $ IterationNumber * 1MB). La durée d'exécution des deux tests était de 50,398 secondes et de 50,843 secondes consécutives, ce qui correspond très probablement aux marges d'erreur acceptables.

La récupération de mémoire des chaînes qui ne sont plus référencées semble assez immédiate, même sans jamais quitter la portée. Étant donné que les chaînes sont mutables, aucune mémoire supplémentaire n’est réellement nécessaire par la suite.

TOUJOURS , les tests suivants ont montré que l'utilisation de la mémoire de pointe est différente PENDANT , les chaînes sont concaténées.

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

Résultat = 10 806 800 octets (~ 10 Mo sans l'empreinte mémoire initiale de PHP)

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

Résultat = 6 613 320 octets (~ 6 Mo sans l'empreinte mémoire initiale de PHP)

Il existe donc une différence qui pourrait être significative dans les très grandes concaténations de chaînes mémoire (je me suis déjà heurté à de tels exemples lors de la création de très grands ensembles de données ou de requêtes SQL).

Mais même ce fait est discutable en fonction des données. Par exemple, concaténer 1 caractère sur une chaîne pour obtenir 50 millions d'octets (soit 50 millions d'itérations) a nécessité un maximum de 50 322 512 octets (~ 48 Mo) en 5,97 secondes. En effectuant cette méthode, array a fini par utiliser 7 337 107 176 octets (~ 6,8 Go) pour créer le tableau en 12,1 secondes, puis a pris 4,32 secondes supplémentaires pour combiner les chaînes du tableau.

Quoiqu'il en soit ... le code ci-dessous est le code de référence mentionné au début, qui montre que les méthodes sont à peu près égales. Il génère un joli tableau 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>';
}
?>

Tout d'abord, si vous n'avez pas besoin de concaténer les chaînes, ne le faites pas: cela sera toujours plus rapide

echo $a,$b,$c;

que

echo $a . $b . $c;

Cependant, au moins en PHP5, la concaténation de chaînes est très rapide, surtout s’il n’ya qu’une référence à une chaîne donnée. Je suppose que l'interprète utilise une technique interne StringBuilder .

Si vous placez des valeurs de variable dans des chaînes PHP, je comprends qu'il est légèrement plus rapide d'utiliser l'inclusion de variable en ligne (ce n'est pas son nom officiel - je ne me souviens plus de quoi il s'agit)

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

Doit être entre guillemets pour fonctionner. Fonctionne également pour les membres du groupe (c'est-à-dire

echo "You requested page id {

Si vous placez des valeurs de variable dans des chaînes PHP, je comprends qu'il est légèrement plus rapide d'utiliser l'inclusion de variable en ligne (ce n'est pas son nom officiel - je ne me souviens plus de quoi il s'agit)

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

Doit être entre guillemets pour fonctionner. Fonctionne également pour les membres du groupe (c'est-à-dire

<*>

)

POST['id']}";

)

pas de telle limitation en php, php peut concaténer une chaîne avec l'opérateur point (.)

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

sorties "bonjour le monde"

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top