本篇文章跟大家介紹一下PHP的引用數。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。
在PHP的資料結構中,引用計數就是指每一個變量,除了保存了它們的型別和值之外,還額外保存了兩個內容,一個是當前這個變數是否被引用,另一個是引用的次數。為什麼要多存這樣兩個內容呢?當然是為了垃圾回收(GC)。
也就是說,當引用次數為0的時候,這個變數就沒有再被使用了,就可以透過 GC 來進行回收,釋放佔用的記憶體資源。
任何程式都不能無限制的一直佔用著記憶體資源,過大的記憶體佔用往往會帶來一個嚴重的問題,那就是記憶體洩露,而GC 就是PHP底層自動幫我們完成了記憶體的銷毀,不用像C 一樣必須去手動地free 。
我們需要安裝xdebug 擴展,然後使用xdebug_debug_zval() 函數就可以看到指定記憶體的詳細資訊了,例如:
$a = "I am a String"; xdebug_debug_zval('a'); // a: (refcount=1, is_ref=0)='I am a String'
從上述內容可以看出,這個$a 變量的內容是I am a String 這樣一個字串。而括號內的 refcount 就是引用次數,is_ref 則是說明這個變數是否被引用。我們透過變數賦值來看看這個兩個參數是如何變化的。
$b = $a; xdebug_debug_zval('a'); // a: (refcount=1, is_ref=0)='I am a String' $b = &$a; xdebug_debug_zval('a'); // a: (refcount=2, is_ref=1)='I am a String'
當我們進行普通賦值後,refcount 和 is_ref 沒有任何變化,但當我們進行引用賦值後,可以看到 refcount 變成了2,is_ref 變成了1。這也就是說明目前的 \$a 變數被引用賦值了,它的記憶體符號表服務於 $a 和 $b 兩個變數。
$c = &$a; xdebug_debug_zval('a'); // a: (refcount=3, is_ref=1)='I am a String' unset($c, $b); xdebug_debug_zval('a'); // a: (refcount=1, is_ref=1)='I am a String' $b = &$a; $c = &$a; $b = "I am a String new"; xdebug_debug_zval('a'); // a: (refcount=3, is_ref=1)='I am a String new' unset($a); xdebug_debug_zval('a'); // a: no such symbol
繼續增加一個 $c 的引用賦值,可以看到 refcount 會繼續增加。然後unset 掉$b 和$c 之後,refcount 恢復到了1,不過這時需要注意的是,is_ref 依然還是1,也就是說,這個變數被引用過,這個is_ref 就會變成1,即使引用的變量都已經unset 掉了這個值依然不變。
最後我們 unset 掉 $a ,顯示的就是 no such symbol 了。當前變數已經被銷毀不是一個可以用的符號引用了。 (注意,PHP中的變數對應的是記憶體的符號表,並不是真正的記憶體位址)
和普通類型的變數一樣,物件變數也是使用同樣的計數規則。
// 对象引用计数 class A{ } $objA = new A(); xdebug_debug_zval('objA'); // objA: (refcount=1, is_ref=0)=class A { } $objB = $objA; xdebug_debug_zval('objA'); // objA: (refcount=2, is_ref=0)=class A { } $objC = $objA; xdebug_debug_zval('objA'); // objA: (refcount=3, is_ref=0)=class A { } unset($objB); class C{ } $objC = new C; xdebug_debug_zval('objA'); // objA: (refcount=1, is_ref=0)=class A { }
不過這裡需要注意的是,物件的符號表是建立的連接,也就是說,對$objC 進行重新實例化或修改為NULL ,並不會影響$objA 的內容,這方面的知識我們在之前的物件賦值在PHP到底是不是引用?文章中已經有說明。物件進行普通賦值運算也是引用型別的符號表賦值,所以我們不需要加 & 符號。
// 数组引用计数 $arrA = [ 'a'=>1, 'b'=>2, ]; xdebug_debug_zval('arrA'); // arrA: (refcount=2, is_ref=0)=array ( // 'a' => (refcount=0, is_ref=0)=1, // 'b' => (refcount=0, is_ref=0)=2 // ) $arrB = $arrA; $arrC = $arrA; xdebug_debug_zval('arrA'); // arrA: (refcount=4, is_ref=0)=array ( // 'a' => (refcount=0, is_ref=0)=1, // 'b' => (refcount=0, is_ref=0)=2 // ) unset($arrB); $arrC = ['c'=>3]; xdebug_debug_zval('arrA'); // arrA: (refcount=2, is_ref=0)=array ( // 'a' => (refcount=0, is_ref=0)=1, // 'b' => (refcount=0, is_ref=0)=2 // ) // 添加一个已经存在的元素 $arrA['c'] = &$arrA['a']; xdebug_debug_zval('arrA'); // arrA: (refcount=1, is_ref=0)=array ( // 'a' => (refcount=2, is_ref=1)=1, // 'b' => (refcount=0, is_ref=0)=2, // 'c' => (refcount=2, is_ref=1)=1 // )
調試數組的時候,我們會發現兩個比較有趣的事情。
一是數組內部的每個元素又有單獨的自己的引用計數。這也比較好理解,每一個數組元素都可以看做是一個單獨的變量,但數組就是這堆變量的一個哈希集合。如果在物件中有成員變數的話,也是一樣的效果。當陣列中的某一個元素被 & 引用賦值給其他變數之後,這個元素的 refcount 會增加,不會影響整個陣列的 refcount 。
二是數組預設上來的 refcount 是2。其實這是 PHP7 之後的新的特性,當陣列定義並初始化後,會將這個陣列轉變成一個不可變陣列(immutable array)。為了和普通陣列區分開,這種陣列的 refcount 是從2開始起步的。當我們修改這個數組中的任何元素後,這個數組就會變回普通數組,也就是 refcount 會變回1。這個大家可以自己嘗試下,關於為什麼要這樣做的問題,官方的解釋是為了效率,具體的原理可能還是需要深挖 PHP7 的源碼才能知道。
其實PHP 在底層已經幫我們做好了GC 機制就不需要太關心變數的銷毀釋放問題,但是,千萬要注意的是物件或陣列中的元素是可以賦值為自身的,也就是說,給某個元素賦值一個自身的參考就變成了循環引用。那麼這個物件基本上就不太可能被 GC 自動銷毀了。
// 对象循环引用 class D{ public $d; } $d = new D; $d->d = $d; xdebug_debug_zval('d'); // d: (refcount=2, is_ref=0)=class D { // public $d = (refcount=2, is_ref=0)=... // } // 数组循环引用 $arrA['arrA'] = &$arrA; xdebug_debug_zval('arrA'); // arrA: (refcount=2, is_ref=1)=array ( // 'a' => (refcount=0, is_ref=0)=1, // 'b' => (refcount=0, is_ref=0)=2, // 'arrA' => (refcount=2, is_ref=1)=... // )
不管是物件還是數組,在列印偵錯時出現了 ... 這樣的省略號,那麼你的程式中就出現了循環引用。在之前的文章 關於PHP中物件複製的那點事兒 中我們也講過這個循環引用的問題,所以這個問題應該是我們在日常開發中應該時刻關注的問題。
推薦學習:php影片教學
#以上是PHP的引用計數是什麼意思?的詳細內容。更多資訊請關注PHP中文網其他相關文章!