buffer
buffer是一個記憶體位址空間,Linux系統預設大小一般為4096(4kb),即一個記憶體頁。主要用於儲存速度不同步的裝置或者優先順序不同的裝置之間傳辦理資料的地區。通過buffer,可以使進程這間的相互等待變少。這裡說一個通俗一點的例子,你開啟文字編輯器編輯一個檔案的時候,你每輸入一個字元,作業系統並不會立即把這個字元直接寫入到磁碟,而是先寫入到buffer,當寫滿了一個buffer的時候,才會把buffer中的資料寫入磁碟,當然當調用核心功能flush()的時候,強制要求把buffer中的髒資料寫回磁碟。
同樣的道理,當執行echo,print的時候,輸出並沒有立即通過tcp傳給用戶端瀏覽器顯示, 而是將資料寫入php buffer。php output_buffering機制,意味在tcp buffer之前,建立了一新的隊列,資料必須經過該隊列。當一個php buffer寫滿的時候,指令碼進程會將php buffer中的輸出資料交給系統核心交由tcp傳給瀏覽器顯示。所以,資料會依次寫到這幾個地方:echo/print -> php buffer -> tcp buffer -> browser
php output_buffering
預設情況下,php buffer是開啟的,而且該buffer預設值是4096,即4kb。你可以通過在php.ini設定檔中找到output_buffering配置.當echo,print等輸出使用者資料的時候,輸出資料都會寫入到php output_buffering中,直到output_buffering寫滿,會將這些資料通過tcp傳送給瀏覽器顯示。你也可以通過ob_start()手動啟用php output_buffering機制,使得即便輸出超過了4kb資料,也不真的把資料交給tcp傳給瀏覽器,因為ob_start()將php buffer空間設定到了足夠大。只有直到指令碼結束,或者調用ob_end_flush函數,才會把資料發送給用戶端瀏覽器。
1.當output_buffering=4096,並且輸出較少資料(少於一個buffer)
複製代碼 代碼如下:
<?php
for ($i = 0; $i < 10; $i++) {
echo $i . '<br/>';
sleep($i + 1); //
}
?>
現象:不是每隔幾秒就會有間斷性輸出,而是直到響應結束,才能看一次性看到輸出,在等待伺服器指令碼處理結束之前,瀏覽器介面一直保持空白。這是因為,資料量太小,php output_buffering沒有寫滿。寫資料的順序,依次是echo->php buffer->tcp buffer->browser
2.當output_buffering=0,並且輸出較少資料(少於一個buffer)
複製代碼 代碼如下:
<?php
//通過ini_set('output_buffering', 0)並不生效
//應該編輯/etc/php.ini,設定output_buffering=0禁用output buffering機制
//ini_set('output_buffering', 0); //徹底禁用output buffering功能
for ($i = 0; $i < 10; $i++) {
echo $i . '<br/>';
flush(); //通知作業系統底層,儘快把資料給用戶端瀏覽器
sleep($i + 1); //
}
?>
現象:與剛才顯示並不一致,禁用了php buffering機制之後,在瀏覽器可以斷斷續續看到間斷性輸出,不必等到指令碼執行完畢才看到輸出。這是因為,資料沒有在php output_buffering中停留。寫資料的順序依次是echo->tcp buffer->browser
3.當output_buffering=4096.,輸出資料大於一個buffer,不調用ob_start()
複製代碼 代碼如下:
#//建立一個4kb大小的檔案
$dd if=/dev/zero of=f4096 bs=4096 count=1
<?php
for ($i = 0; $i < 10; $i++) {
echo file_get_contents('./f4096') . $i . '<br/>';
sleep($i +1);
}
?>
現象:響應還沒結束(http串連沒有關閉),斷斷續續可以看到間斷性輸出,瀏覽器介面不會一直保持空白。儘管啟用了php output_buffering機制,但依然會間斷性輸出,而不是一次性輸出,是因為output_buffering空間不夠用。每寫滿一個php buffering,資料就會發送到用戶端瀏覽器。
4.當output_buffering=4096, 輸出資料大於一個tcp buffer, 調用ob_start()
複製代碼 代碼如下:
<?php
ob_start(); //開啟php buffer
for ($i = 0; $i < 10; $i++) {
echo file_get_contents('./f4096') . $i . '<br/>';
sleep($i + 1);
}
ob_end_flush();
?>
現象:直到服務端指令碼處理完成,響應結束,才看到完整輸,輸出間隔時間很短,以至你感受不到停頓。在輸出之前,瀏覽器一直保持著空白介面,等待服務端資料。這是因為,php一旦調用了ob_start()函數,它會將php buffer擴充到足夠大,直到ob_end_flush函數調用或者指令碼運行結速才發送php buffer中的資料到用戶端瀏覽器。
output buffering函數
1.ob_get_level
返回輸出緩衝機制的嵌套層級,可以防止模板重複嵌套自己。
1.ob_start
啟用output_buffering機制。一旦啟用,指令碼輸出不再直接出給瀏覽器,而是先暫時寫入php buffer記憶體地區。
php預設開啟output_buffering機制,只不過,通過調用ob_start()函資料output_buffering值擴充到足夠大。也可以指定$chunk_size來指定output_buffering的值。$chunk_size預設值是0,表示直到指令碼運行結束,php buffer中的資料才會發送到瀏覽器。如果你設定了$chunk_size的大小,則表示只要buffer中資料長度達到了該值,就會將buffer中的資料發送給瀏覽器。
當然,你可以通過指定$ouput_callback,來處理buffer中的資料。比如函數ob_gzhandler,將buffer中的資料壓縮後再傳送給瀏覽器。
2.ob_get_contents
擷取一份php buffer中的資料拷貝。值得注意的是,你應該在ob_end_clean()函數調用之前調用該函數,否則ob_get_contents()返回一個Null 字元中。
3.ob_end_flush與ob_end_clean
這二個函數有點相似,都會關閉ouptu_buffering機制。但不同的是,ob_end_flush只是把php buffer中的資料沖(flush/send)到用戶端瀏覽器,而ob_clean_clean將php bufeer中的資料清空(erase),但不發送給用戶端瀏覽器。ob_end_flush調用之後,php buffer中的資料依然存在,ob_get_contents()依然可以擷取php buffer中的資料拷貝。而ob_end_clean()調用之後ob_get_contents()取到的是Null 字元串,同時瀏覽器也接收不到輸出,即沒有任何輸出。
慣用案例
常常在一些模板引擎和分頁檔緩衝中看到ob_start()使用。下面濕CI中載入模板的程式碼:
複製代碼 代碼如下:
<SPAN style="WHITE-SPACE: pre"> </SPAN>/*
* Buffer the output
*
* We buffer the output for two reasons:
* 1. Speed. You get a significant speed boost.
* 2. So that the final rendered template can be
* post-processed by the output class. Why do we
* need post processing? For one thing, in order to
* show the elapsed page load time. Unless we
* can intercept the content right before it's sent to
* the browser and then stop the timer it won't be accurate.
*/
ob_start();
// If the PHP installation does not support short tags we'll
// do a little string replacement, changing the short tags
// to standard PHP echo statements.
if ((bool) @ini_get('short_open_tag') === FALSE AND config_item('rewrite_short_tags') == TRUE)
{
//替換短標記<?=***>
echo eval('?>'.preg_replace("/;*\s*\?>/", "; ?>", str_replace('<?=', '<?php echo ', file_get_contents($_ci_path))));
}
else
{
include($_ci_path); // include() vs include_once() allows for multiple views with the same name
}
//記錄調試資訊
log_message('debug', 'File loaded: '.$_ci_path);
// Return the file data if requested
if ($_ci_return === TRUE)
{
$buffer = ob_get_contents();
@ob_end_clean();
return $buffer;
}
/*
* Flush the buffer... or buff the flusher?
*
* In order to permit views to be nested within
* other views, we need to flush the content back out whenever
* we are beyond the first level of output buffering so that
* it can be seen and included properly by the first included
* template and any subsequent ones. Oy!
*
*/
if (ob_get_level() > $this->_ci_ob_level + 1)
{
ob_end_flush();
}
else
{
//將模板內容添加到輸出資料流中
$_ci_CI->output->append_output(ob_get_contents());
//清除buffer
@ob_end_clean();
}