Вопрос

В таких языках, как Java и C #, строки неизменяемы, и построение строки по одному символу за раз может быть дорогостоящим с точки зрения вычислений.В указанных языках существуют библиотечные классы для снижения этой стоимости, такие как C# System.Text.StringBuilder и Java java.lang.StringBuilder.

Работает ли php (4 или 5;Меня интересуют оба) разделяете это ограничение?Если да, то существуют ли аналогичные решения этой проблемы?

Это было полезно?

Решение

Нет, в 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
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

Когда вы проводите временное сравнение, различия настолько малы, что это не очень важно.Было бы разумнее выбрать тот вариант, который облегчит чтение и понимание вашего кода.

Аналог StringBuilder в PHP не нужен.

Я провел пару простых тестов:

в 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, обычная конкатенация:~6 мс
2) PHP, использующий StringBuilder:~5 мс
3) C #, обычная конкатенация:~520 мс
4) C #, использующий StringBuilder:~1 мс

100000 итераций:
1) PHP, обычная конкатенация:~ 63 мс
2) PHP, использующий StringBuilder:~555 мс
3) C #, обычная конкатенация:~91000 мс // !!!
4) C #, использующий StringBuilder:~17 мс

Я знаю, о чем ты говоришь.Я только что создал этот простой класс для эмуляции класса Java StringBuilder.

class StringBuilder {

  private $str = array();

  public function __construct() { }

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

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

}

Строки 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 str1,str2,str3 

вместо того, чтобы

echo str1.str2.str3 
чтобы сделать это немного быстрее.

Я написал код в конце этого поста, чтобы протестировать различные формы конкатенации строк, и все они действительно почти в точности равны как по объему памяти, так и по времени.

Два основных метода, которые я использовал, - это объединение строк друг с другом и заполнение массива строками, а затем их сжатие.Я добавил 500 строк со строкой размером 1 МБ в php 5.6 (в результате получилась строка размером 500 МБ).На каждой итерации теста все следы памяти и времени были очень-очень близки (~ $IterationNumber * 1 МБ).Время выполнения обоих тестов составило 50,398 секунды и 50,843 секунды подряд, что, скорее всего, находится в пределах допустимой погрешности.

Сборка мусора из строк, на которые больше нет ссылок, кажется, происходит довольно быстро, даже не выходя за пределы области видимости.Поскольку строки изменяемы, дополнительная память на самом деле не требуется постфактум.

ОДНАКО, Следующие тесты показали , что существует разница в пиковом использовании памяти В ТО ВРЕМЯ КАК строки объединяются.

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

Результат= 10 806 800 байт (~ 10 МБ без первоначального объема памяти PHP)

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

Результат = 6 613 320 байт (~ 6 МБ без первоначального объема памяти PHP)

Таким образом, на самом деле существует разница, которая может быть существенной при очень-очень больших конкатенациях строк с точки зрения памяти (я сталкивался с такими примерами при создании очень больших наборов данных или SQL-запросов).

Но даже этот факт является спорным в зависимости от имеющихся данных.Например, объединение 1 символа в строку для получения 50 миллионов байт (таким образом, 50 миллионов итераций) заняло максимальный объем 50 322 512 байт (~ 48 МБ) за 5,97 секунды.При выполнении метода array в итоге было использовано 7,337,107,176 байт (~ 6,8 ГБ) для создания массива за 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>';
}
?>

Во-первых, если вам не нужно, чтобы строки были объединены, не делайте этого:это всегда будет быстрее сделать

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']}";

)

в php такого ограничения нет, php может объединять strng с оператором dot(.)

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

выходные данные "hello world"

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top