مدیریت حافظه درPHP - دستور Foreach
بسم
الله الرحمن الرحیم
برخی
توسعهدهنده گان، و یا حتی توسعهدهنده
گان با تجربه نیز از نحوه مدیریت آرایه
ها در حلقه های foreach توسط php بطور
دقیق اطلاعی ندارن!
/ شایدم
دارن حالا :)
/ بطور
پیشفرض تو foreach ،
خود php یه
کپی از آرایه ای که در حلقه استفاده می شه
می گیره و کپی گرفته شده هم به محق اینکه
عملیات foreach تموم
می شه از بین می ره. که
این موضوع در عملیات ساده در حلقه foreach به
وضوع روشنه.
به
عنوان مثال:
$set
= array("apple", "banana", "coconut");
foreach
( $set AS $item )
{
echo
"{$item}\n";
}
و
خروجی:
apple
banana
coconut
حتی
با وجود اینکه از آرایه کپی شده، توسعهدهنده
گاهی به این موضوع توجه نمی کنه، برای
اینکه به آرایه اصلی نه در بین حلقه (عملیات) و
نه بعد از اتمام حلقه ها اشارهای نمی
شه. هرچند
که وقتی شما سعی میکنید عناصر آرایه رو
بین حلقه ها ویرایش کنین و بعد از اتمام
عملیات متوجه می شین که تغییری در آرایه
اصلی ایجاد نشده:
$set
= array("apple", "banana", "coconut");
foreach
( $set AS $item )
{
$item
= strrev ($item);
}
print_r($set);
و
خروجی:
Array
(
[0]
=> apple
[1]
=> banana
[2]
=> coconut
)
هیچ
تغییری تو آرایه اصلی اعمال نشده. حتی
با وجود اینکه بطور صریح مقداری رو
به $item اختصاص
دادیم. دلیل
این موضوع هم به این خاطره که، عملیاتی
که روی $item داریم
انجام می دیم، در حقیقت این عملیات روی
کپی گرفته شده از آرایه اصلی داره اتفاق
می افته. کپی
ای که php خودش
بطور پیشفرض از آرایه مون گرفته.
برای
نقض این مسأله که بتونیم عناصر آرایه اصلی
رو تغییر بدیم، می تونیم $item رو
بوسیله رفرنس (reference) بکار
ببریم:
$set
= array("apple", "banana", "coconut");
foreach
( $set AS &$item )
{
$item
= strrev($item);
}
print_r($set);
و
خروجی:
Array
(
[0]
=> elppa
[1]
=> ananab
[2]
=> tunococ
)
همونطور
که می بینین، وقتی عملیاتی روی $item که
بوسیله رفرنس صدا زده شده، اعمال می شه،
تغییرات اعمال شده به $item مستقیم
روی عنصر مربوطه در آرایه اصلی اعمال می
شه. همچنین
استفاده از $item بوسیله
رفرنس از کپی گرفته شدن آرایه توسط php جلوگیری
می کنه. برای
تست صحت این موضوع، ابتدا یه اسکریپتی
برای به نمایش درآوردن آرایه کپی شده به
شما نشون می دیم:
$set
= array("apple", "banana", "coconut");
foreach
( $set AS $item )
{
$set[]
= ucfirst($item);
}
print_r($set);
و
خروجی:
Array
(
[0]
=> apple
[1]
=> banana
[2]
=> coconut
[3]
=> Apple
[4]
=> Banana
[5]
=> Coconut
)
در
این مثال، php از
آرایه $set کپی
گرفته تا عملیات رو روی کپی انجام بده،
اما وقتی که $set داخل
حلقه استفاده شده، php عناصر
جدیدی رو به آرایه اصلی اضافه کرده و نه
به آرایه ای که ازش کپی گرفته بود.
بطور
ساده تر، php فقط
از آرایه کپی شده برای اجرای حلقه ها و
اعمال تغییرات به $item استفاده
می کنه. به
این خاطر حلقه فوق، فقط برای ۳ بار اجرا
می شه و هر بار عنصر دیگری رو به آخر آرایه
اصلی اضافه می کنه، که باعث می شه آرایه
اصلی دارای 6 تا
عنصر بشه ولی هیچ وقت داخل حلقه بی نهایت
نمی افته.
حالا
مسئله اینه که، چطور میشد اگه $item رو
بوسیله رفرنس استفاده کرده بودیم؟
برای
جواب این سوال، کاراکتر & رو
به مثال فوق اضافه میکنیم و ببینیم نتیجه
چه خواهد بود:
$set
= array("apple", "banana", "coconut");
foreach
( $set AS &$item )
{
$set[]
= ucfirst($item);
}
print_r($set);
همونطور
که احتمالاً حدس زدید، استفاده از رفرنس
در این کد بالا باعث می شه تو حلقه نامحدود
بیفتیم، توجه کنید بخاطر اینکه این یه
حلقه بی نهایته، یا خودتون باید اسکریپت
رو از کار بندازین و یا صبر کنین که سیستم
عاملتون رمش کم بیاد و گیر کنه و احتمالاً
هم ریسیت می شه :)
. واسه
این موضوغ تکه کد زیر رو می تونید به
اسکریپت تون اضافه کنید که فوراً php از
رم کم بیاره و متوقف بشه که برای حلقه های
بی نهایت، توصیه می شه تکه کد زیر رو به
اسکریپت تون اضافه کنین:
ini_set("memory_limit","1M");
در
مثال قبلی که حلقه بی نهایتی بود، دیدیم
که چرا PHP طوری
نوشته شده که کپی ای از آرایه اصلی بگیره
و روی اون عملیاتشو انجام بده. وقتی
کپی ای گرفته می شه و فقط توسط خود حلقه
استفاده می شه، آرایه اصلی در سراسر اجرای
حلقه ها ثابت می مونه و در نتیجه با همچین
مشکلاتی روبرو نمی شیم.
اما
موضوع فقط به همینجا ختم نمی شه. در
صورتی که آرایه مون رو بصورت رفرنس در
جایی از اسکریپت استفاده کرده باشیم PHP نمی
تونه از آرایه کپی بگیره. طبق
سناریویی که در بالا داشتیم، می دونیم که
استفاده از $item بصورت
رفرنس باعث حلقه نامحدود می شه، اما
اگه $set در
جای دیگه ای از اسکریپت مون بصورت رفرنس
استفاده شده باشه، حتی در صورتی که از
رفرنس در خود foreach استفاده
نشده باشه هم باعث شکست می شه و با مشکل
روبرو می شیم:
$set
= array("apple", "banana", "coconut");
$a
= &$set;
foreach
( $set AS $item )
{
$set[]
= ucfirst($item);
}
که
باعث می شه تو حلقه نامحدودی بیفته، حتی
بازم اگه $item بوسیله
رفرنس استفاد نشده باشه. استفاده
از $a بجای $set در foreach هم
همین نتایج یکسانی رو در بر خواهد داشت و
فرقی نمی کنه.
این
به این معنی نیست که بگیم $item بطور
غیرمستقیم توسط رفرنس استفاده شده وقتی
که $set جای
دیگه ای بصورت رفرنس استفاده شده باشه. برای
اثبات این قضیه، مثال زیر رو ببینید:
$set
= array("apple", "banana", "coconut");
$a
= &$set;
foreach
( $a AS $item )
{
$item
= ucfirst($item);
}
print_r($set);
و
خروجی:
Array
(
[0]
=> apple
[1]
=> banana
[2]
=> coconut
)
همونطور
که میبینید مقدار اولیه عنصرهای
آرایه $set تغییر
نکرده، به این خاطر که حتی با وجود
اینکه $set توسط
رفرنس استفاده شده است، و $set کپی
هم نشده، به $item فقط
محدوده lexical یا
همون (lexical
scope) در
رابطه با حلقه ای که توش هست داده شده و
تغییرات اعمال شده رو به $set برگشت
نخواهد داد. و
هنوزم در صورتی که می خوایید تغییرات بر
روی آرایه اصلی اعمال بشه باید از رفرنس
استفاده کنید.
به
مثال زیر دقت کنین:
$set
= array("apple", "banana", "coconut");
$a
= &$set;
foreach
( $a AS &$item )
{
$item
= strrev($item);
}
print_r($set);
و
خروجی زیر رو خواهم داشت:
Array
(
[0]
=> elppa
[1]
=> ananab
[2]
=> tunococ
)
همه
این مثالهای فوق در آرایه های انجمنی
هم در foreach به
سینتکس ($set
AS $key => $item) صادقن
و کار می کنن. همچنین $key هیچ
وقت نمی تونه توسط رفرنس استفاده بشه و
در صورتی که استفاده بشه با خطای Fatal رو
برو خواهیم شد. در
نتیجه این تکنیک هایی که برای ویرایش
مقدار عناصر آرایه (مقادیر
و نه کلیدها) استفاده
شدن، برای ویرایش کلید های عناصر آرایه
جواب نخواهد داد. هر
چند که می تونید کلید های جدیدی در آرایه
ایجاد کرده و یا کلید های موجود در آرایه
رو مثل مثال زیر حذف کنین:
$set
= array("apple"=>"red","banana"=>"yellow","coconut"=>"brown");
foreach
( $set AS $key => $item )
{
$set[ucfirst($key)]
= $item;
unset($set[$key]);
}
print_r($set);
و
خروجی هم:
Array
(
[Apple]
=> red
[Banana]
=> yellow
[Coconut]
=> brown
)
همانطورکه
متوجه شدید، آرایه قبل از شروع حلقه کپی
شده. ولی
اگه در شرایطی بودید که به هر دلیلی نمی
شد از آرایه کپی گرفت، در این صورت با مشکل
روبرو می شدید:
$set
= array("apple"=>"red","banana"=>"yellow","coconut"=>"brown");
$a
= &$set;
foreach
( $set AS $key => $item )
{
$set[ucfirst($key)]
= $item;
unset($set[$key]);
}
print_r($set);
و
خروجی:
Array
(
)
برای
اینکه قبلاً به آرایه اصلی رفرنسی شده
بود و ازش در foreach کپی
هم نشده است، و در نتیجه وقتی سعی در تغییر
ساختار فیزیکی آرایه میگیرید با نتایج
پیشبینی نشدهای روبرو می شید، بخصوص
هنگام استفاده از تابع unset().
بدون
استفاده از تابع unset() در
این مثال، شما در واقع عملیات رو بر روی
آرایه اصلی انجام میدید و حلقه ها هم از
روی آرایه اصلی و به همون تعداد خواهد بود
پس در نتیجه با حلقه بی نهایت روبرو خواهید
شد، اما از اونجایی که برای $set کلید ($key) رو
مشخص میکنیم پس در نتیجه تو حلقه بی
نهایت هم نخواهد افتاد.
به
مثال زیر توجه کنید:
$set
= array("apple"=>"red","banana"=>"yellow","coconut"=>"brown");
$a
= &$set;
foreach
( $set AS $key => $item )
{
$set[ucfirst($key)]
= $item;
}
print_r($set);
و
خروجی مون هم:
Array
(
[apple]
=> red
[banana]
=> yellow
[coconut]
=> brown
[Apple]
=> red
[Banana]
=> yellow
[Coconut]
=> brown
)
برای
اینکه ثابت کنیم هنوزم امکان افتادن تو
حلقه نامحدود از همین تکه کد بالا وجود
داره، تکه کد $set[] رو
داخل حلقه مون اضافه می کنیم:
$set
= array("apple"=>"red","banana"=>"yellow","coconut"=>"brown");
$a
= &$set;
foreach
( $set AS $key => $item )
{
$set[ucfirst($key)]
= $item;
$set[]
= $item;
}
print_r($set);
که
باعث ایجاد حلقه بی نهایت می شه.
از
کاراهای جالبی که می شه توسط همین
سینتکس $key
=> $item زمانی
که آرایه کپی شده باشه و بدون ترس از اینکه
مشکلی پیش بیاد انجام داد، تغییر ساختار
آرایه اصلی هست:
$set
= array("apple"=>"red","banana"=>"yellow","coconut"=>"brown");
foreach
( $set AS $key => $item )
{
$set[]
= ucfirst($item);
unset($set[$key]);
}
print_r($set);
و
خروجی زیر رو خواهیم داشت:
Array
(
[0]
=> Red
[1]
=> Yellow
[2]
=> Brown
)
همانطور
که در این مثال می بینید، از آرایه اصلی،
برای استفاده در حلقه کپی گرفته شده
بود. و $set های
داخل حلقه به نسخه خارجی یا همون آرایه
اصلی خارج از حلقه اشاره داره، در نتیجه
استفاده از تابع unset() و
تکه کد $set[] برای
افزودن روی آرایه اصلی به ما آرایه جدید
شامل عناصر با مقادیر ای که با حروف بزرگ
شروع شدهاند بدون کلید هاشون تحویل می
ده.
و
البته با کمی تغییر در کد فوق می تونیم
کلید ها رو هم حفط کنیم:
$set
= array("apple"=>"red","banana"=>"yellow","coconut"=>"brown");
foreach
( $set AS $key => $item ) {
unset($set[$key]);
$set[$key]
= ucfirst($item);
}
print_r($set);
خروجی
هم:
Array
(
[apple]
=> Red
[banana]
=> Yellow
[coconut]
=> Brown
)
این
اطلاعات بیشتر برای توسعه دهندگانی مفید
می تونه باشه که قصد دارن حفره های مربوط
به حافظه و موجود تو اسکریپت های نوشته
شده با PHP رو
بگیرن. اگر
شما از دستور foreach برای
آرایه ای از آبجکت ها که می تونن حجم
حدود 50 مگ
رو داشته باشن استفاده می کنید، در حقیقت
بدون دلیل دارید کپی ای از همون ساختار
تو حافظه ایجاد می کنید. اگه
تو حلقه ها قصد تغییر ساختار آرایه رو
ندارید و یا عنصر جدیدی بهش اضافه نخواهید
کرد، بهتر خواهد بود که از تکه کد $a
= &$array; استفاده
کنید تا کپی ای بدون دلیل از آرایه گرفته
نشه و بدون دلیل هم منابع سیستم استفاده
نشه.
همینطور
این اطلاعات می تونه برای توسعهدهنده
گانی که نمی تونن درک واضحی از عملکرد
آرایه ها از اینکه چرا اونطوری رفتار می
کنن داشته باشن هم مفید باشه. و
بطور سادهتر، اگه شما از رفرنس ها استفاده
نمی کنید، حلقه به هر تعداد عنصر اولیه
ای که تو آرایه اصلی وجود داره اجرا می شه
بدون در نظر گرفتن اینکه بعدا چه بلایی
سر آرایه اصلی در می یارید.
نکته
دیگه ای که در مورد آرایه ها با حجم زیاد
هست، می تونیم از کلاسهای استاندارد
خود PHP هم
مثل arrayIterator استفاده
کنیم. +
منبع
مطلب: +
امیدوارم
مفید بوده باشه :)