Pergunta

Em linguagens como Java e C #, strings são imutáveis ??e pode ser computacionalmente caro construir uma cadeia de um carácter de cada vez. No referido línguas, existem classes de biblioteca para reduzir esse custo, como C # System.Text.StringBuilder e java.lang.StringBuilder Java.

O PHP (4 ou 5; Estou interessado em ambos) compartilhar essa limitação? Se assim for, existem soluções semelhantes para o problema disponíveis?

Foi útil?

Solução

Não, não há nenhum tipo de classe stringbuilder em PHP, uma vez cordas são mutáveis.

Dito isto, há diferentes maneiras de construir uma string, dependendo do que você está fazendo.

eco, por exemplo, irá aceitar fichas separados por vírgulas para a saída.

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

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

O que isto significa é que você pode produzir uma seqüência complexa sem realmente usar concatenação, o que seria mais lento

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

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

Se você precisa capturar essa saída em uma variável, você pode fazer isso com os funções buffer de saída .

Além disso, o desempenho do conjunto de PHP é realmente bom. Se você quer fazer algo como uma lista separada por vírgulas de valores, basta usar implode ()

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

Por fim, certifique-se de se familiarizar com do PHP tipo string e é diferente delimitadores, eo implicações de cada um.

Outras dicas

Eu estava curioso sobre isso, então eu corri um teste. Eu usei o seguinte código:

<?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 obteve os seguintes resultados. Imagem anexada. Claramente, sprintf é a maneira menos eficiente de fazê-lo, tanto em termos de tempo e consumo de memória. EDIT: visualização da imagem em outra guia a menos que tenha visão de águia. enter descrição da imagem aqui

Quando você faz uma comparação cronometrada, as diferenças são tão pequenas que não é muito relevante. Não faria mais uma vez ir para a escolha que torna seu código mais fácil de ler e entender.

StringBuilder analógico não é necessária em PHP.

Eu fiz um par de testes simples:

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

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

Resultados:

10000 iterações:
1) PHP, concatenação comum: ~ 6ms
2) PHP, usando StringBuilder: ~ 5 ms
3) C #, concatenação comum: ~ 520ms
4) C #, usando StringBuilder: ~ 1 ms

100000 iterações:
1) PHP, concatenação comum: ~ 63ms
2) PHP, usando StringBuilder: ~ 555ms
3) C #, concatenação comum: ~ 91000ms // !!!
4) C #, usando StringBuilder: ~ 17ms

Eu sei o que você está falando. Acabei de criar esta classe simples para emular a 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);
  }

}

cordas PHP são mutáveis. Você pode alterar caracteres específicos como este:

$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 você pode acrescentar caracteres para uma seqüência como esta:

$string .= 'a';

Sim. Eles fazem. Por exemplo, se você quiser eco par de cordas juntos, uso

echo str1,str2,str3 

em vez de

echo str1.str2.str3 
para obtê-lo um pouco mais rápido.

Eu escrevi o código no final deste post para testar as diferentes formas de concatenação e eles realmente são quase exatamente igual em ambos os pegadas de memória e de tempo.

Os dois métodos principais que são utilizados concatenar cadeias uns sobre os outros, e o enchimento de uma matriz com cordas e, em seguida, a implodir eles. Eu fiz 500 adições de corda com uma corda 1MB em PHP 5.6 (por isso o resultado é uma seqüência de 500MB). Em cada iteração do teste, todas as pegadas de memória e tempo foram muito, muito perto (em ~ $ IterationNumber * 1MB). O tempo de execução de ambos os testes foi de 50.398 segundos e 50.843 segundos consecutivos que são mais provável dentro de margens de erro aceitáveis.

A coleta de lixo de cordas que não são mais referenciados parece ser bastante imediato, mesmo sem sair do escopo. Como as cordas são mutáveis, sem memória extra é realmente necessário após o fato.

NO ENTANTO , os seguintes testes mostraram que há uma diferente no uso de memória de pico enquanto as cordas estão sendo concatenados.

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

= Resultado 10,806,800 bytes (~ 10MB W / o espaço de memória inicial PHP)

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

= Resultado 6,613,320 bytes (~ 6MB W / o espaço de memória inicial PHP)

Portanto, há, de facto, uma diferença que pode ser significativo em muito, muito grandes concatenações seqüência de memória-wise (eu tenho que correr em tais exemplos ao criar grandes conjuntos de dados ou consultas SQL).

Mas mesmo esse fato é discutível, dependendo dos dados. Por exemplo, concatenando 1 caractere em uma string para obter 50 milhões de bytes (para 50 milhões de iterações) tomou uma quantidade máxima de 50,322,512 bytes (~ 48MB) em 5,97 segundos. Ao fazer o método de array acabou usando 7,337,107,176 bytes (~ 6.8GB) para criar a matriz em 12,1 segundos e, em seguida, tomou um extra de 4.32 segundos para combinar as cordas a partir da matriz.

Anywho ... a seguir é o código de referência que eu mencionei no início, que mostra os métodos são praticamente iguais. Ele produz uma tabela HTML bonita.

<?
//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>';
}
?>

Em primeiro lugar, se você não precisa as strings a serem concatenadas, não fazê-lo: ele será sempre mais rápido para fazer

echo $a,$b,$c;

de

echo $a . $b . $c;

No entanto, pelo menos em PHP5, concatenação é realmente muito rápido, especialmente se há apenas uma referência a uma determinada cadeia. Eu acho que o intérprete utiliza um StringBuilder-like técnica internamente.

Se você é os valores das variáveis ??de colocação nas strings PHP, eu entendo que é um pouco mais rápido para uso em linha inclusão variável (que não é o seu nome oficial - Não me lembro o que é)

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

Deve estar dentro de aspas duplas ao trabalho. Também funciona para os membros da matriz (ou seja

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

)

nenhuma limitação em php, php pode concatenar string com o ponto (.) operador

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

saídas "Olá mundo"

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top