سؤال

في لغات مثل Java وC#، تكون السلاسل غير قابلة للتغيير وقد يكون إنشاء سلسلة بحرف واحد في كل مرة مكلفًا من الناحية الحسابية.باللغات المذكورة توجد فصول مكتبية لتقليل هذه التكلفة مثل C# System.Text.StringBuilder وجافا java.lang.StringBuilder.

هل PHP (4 أو 5؛أنا مهتم بكليهما) هل تريد مشاركة هذا القيد؟إذا كان الأمر كذلك، فهل هناك حلول مماثلة للمشكلة المتاحة؟

هل كانت مفيدة؟

المحلول

لا، لا يوجد نوع من فئة منشئ السلاسل في PHP، نظرًا لأن السلاسل قابلة للتغيير.

ومع ذلك، هناك طرق مختلفة لبناء سلسلة، اعتمادًا على ما تفعله.

الصدى، على سبيل المثال، سيقبل الرموز المفصولة بفواصل للإخراج.

// 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*1MB).كان وقت تشغيل كلا الاختبارين 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).

ولكن حتى هذه الحقيقة قابلة للنقاش اعتمادا على البيانات.على سبيل المثال، استغرق تسلسل حرف واحد في سلسلة للحصول على 50 مليون بايت (أي 50 مليون تكرار) حدًا أقصى قدره 50,322,512 بايت (~48 ميجابايت) في 5.97 ثانية.أثناء تنفيذ أسلوب المصفوفة، انتهى الأمر باستخدام 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;

مخرجات "مرحبا بالعالم"

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top