PHP ob_flush 的机制及详解
php 缓冲区操作函数
ob_start() // 开启大小无限制的缓冲区
ob_clean() / ob_flush() // 清空/冲出 当前缓冲区
ob_get_contents() // 获取 当前缓冲区的内容
ob_end_clean() / ob_end_flush() // 清空/冲出 并关闭 当前缓冲区
tcp 缓冲区操作函数
flush() // 将数据强制输出至客户端
当我们完成一次请求并从服务器取的数据显示在浏览器页面时,数据其实经过了两层数据 buffer
- php 自己的数据 buffer(默认 1024 4 Bytes),操作函数以 ob_ 开头的系列函数。
- web服务器的数据
buffer:apache 是调用系统内核的 tcp buffer 模块(默认 1024 * 4 Bytes,nginx 为
fastcgi_buffer_size(参数配置)当我们调用 echo 等输出数据时,数据并不会被立即返回给浏览器,php
会将其缓存到自身的 buffer 中,而后冲出至 tcp buffer 中,最后才是输出至浏览器。另外要强调的一点是,php
对每次请求都会自动的开启一个 buffer,这个 buffer 的大小为 php
的配置项:output_buffering(如果我们手动调用的 ob_start 函数,则我们手动开启的 buffer size
是没有限制的)。 当 php 缓存的数据大于这个参数时,php 会把数据输出到 tcp buffer 中,tcp buffer
也会根据当前缓存区数据大小决定是否输出到浏览器。
program data >> php buffer >> tcp buffer >> client
flush()
flush() 函数是将 tcp buffer 数据强制输出到客户端,而非等缓冲区满后自动输出。tcp buffer 的默认值是 4KB,也就是说在数据未缓冲满时是不会发送给客户端的,直至脚本执行结束。如果脚本仍在执行中,缓冲区已满,则会将数据输出到浏览器。我们可以通过 flush() 函数将 tcp buffer 的数据强制输出到浏览器。
但是从实际情况上来说,flush() 是刷新WebServer的缓冲区,而且只有apache的缓冲区会生效,使用nginx作为WebServer是不用生效的。
原因:
- 首先不能在apache的mod_gpiz或者nginx的gpiz上面工作,如果有必要可以进入php.ini关闭相应设置。
- 还有一点就是apache buffer 以及 nginx buffer 也是有区别的,ngnix 是强制开启buffer的,无法关闭。这就造成PHP flush 针对nginx 是无效的。
ob_flush()
将当前 php 缓冲区的内容输出到 tcp buffer (假设当前缓冲区为一级缓冲区),而不等待缓冲区填满。所以调用顺序应为 ob_flush 到 tcp buffer,再 flush 到浏览器。
ob_start()
开启大小无限制的缓冲区(php 默认开启的缓冲区是受 output_buffering 限制的),所以如果你不手动的 ob_flush/ob_end_flush 的话php是不会把数据输出给 tcp buffer 的,这点要注意。
使用场景
导出几十万行数据生成csv文件
CSV,是Comma Separated Value(逗号分隔值)的英文缩写,通常都是纯文本文件。导出的Excel没有什么高级用法的话,只是做导出数据用建议使用本方法,要比PHPexcel要高效的多。
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="'.$fileName.'.csv"');
header('Cache-Control: max-age=0');
//打开PHP文件句柄,php://output 表示直接输出到浏览器
$fp = fopen('php://output', 'a');
// 加入bom头,防止打开文件乱码
fwrite($fp, "\xEF\xBB\xBF")
//输出Excel列名信息
foreach ($headlist as $key => $value) {
//CSV的Excel支持GBK编码,一定要转换,否则乱码
$headlist[$key] = iconv('utf-8', 'gbk', $value);
}
//将数据通过fputcsv写到文件句柄
fputcsv($fp, $headlist);
//每隔$limit行,刷新一下输出buffer,不要太大,也不要太小
$limit = 5000;
//逐行取出数据,不浪费内存
//$count = count($data);
foreach ($data as $k => $v) {
//刷新一下输出buffer,防止由于数据过多造成问题
if ($k % $limit == 0 && $k!=0) {
ob_flush();
flush();
}
$row = $data[$k];
foreach ($row as $key => $value) {
$row[$key] = iconv('utf-8', 'gbk', $value);
}
fputcsv($fp, $row);
}
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。