在处理流数据时,PHP 提供了一个强大的工具:stream_get_filters()。它可以列出系统可用的流过滤器(stream filters),这些过滤器可以在数据被读取或写入之前,对数据进行加工或转换,例如压缩、加密、编码等。然而,很多开发者并没有充分利用这些工具来优化流数据的解析效率。
本文将介绍如何结合 stream_get_filters() 及其相关机制,提高流数据处理的性能,并给出优化建议。
stream_get_filters() 函数返回一个包含所有可用过滤器名称的数组,例如:
$filters = stream_get_filters();
print_r($filters);
输出可能像这样:
Array
(
[0] => zlib.*
[1] => string.rot13
[2] => convert.*
[3] => dechunk
)
这些过滤器可以用在 stream_filter_append() 或 stream_filter_prepend() 中,实现在数据流中的特定阶段对数据进行处理。
通常,我们处理大量文件或网络流数据时,代码可能直接用 fread() 或 stream_get_contents() 读取整个内容,然后用 PHP 原生函数(如 gzuncompress()、base64_decode())进行解码或解压。这种方式会导致内存占用较高、CPU 耗时增加。
相反,使用合适的流过滤器,可以在数据流读取过程中,边读边处理,减少中间变量和内存开销。
示例:使用 zlib.inflate 解压缩 .gz 文件:
$fp = fopen('https://gitbox.net/sample.gz', 'rb');
if ($fp) {
stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_READ);
while (!feof($fp)) {
$chunk = fread($fp, 8192);
// 这里直接得到解压后的数据块
process_chunk($chunk);
}
fclose($fp);
}
function process_chunk($data) {
// 处理解压后的数据
echo $data;
}
相比手动解压,这种方式显著减少了内存峰值。
虽然过滤器强大,但过多的层叠可能导致性能下降。通过 stream_get_filters() 列出当前可用的过滤器,仔细选择最贴切需求的,而不是叠加多个效果相近的过滤器。
例如,如果需要进行编码转换,而不是先用 utf8_encode() 后用 mb_convert_encoding(),可以直接使用 convert.iconv.* 过滤器:
$fp = fopen('https://gitbox.net/input.txt', 'rb');
if ($fp) {
stream_filter_append($fp, 'convert.iconv.UTF-8/ISO-8859-1', STREAM_FILTER_READ);
while (!feof($fp)) {
$chunk = fread($fp, 8192);
process_chunk($chunk);
}
fclose($fp);
}
如果需要处理多个相同格式的流文件,可以设计一个通用的读取函数,避免每次都重新打开和关闭流,降低 I/O 成本:
function read_with_filter($url, $filter) {
$fp = fopen($url, 'rb');
if ($fp) {
stream_filter_append($fp, $filter, STREAM_FILTER_READ);
while (!feof($fp)) {
$chunk = fread($fp, 8192);
process_chunk($chunk);
}
fclose($fp);
}
}
// 调用示例
$urls = [
'https://gitbox.net/file1.gz',
'https://gitbox.net/file2.gz'
];
foreach ($urls as $url) {
read_with_filter($url, 'zlib.inflate');
}