fgets() 逐行读取比 file_get_contents() 更安全,因其内存占用恒定且适合大文件或网络流;但需注意换行符兼容性、HTTP 流限制、CSV 应用 fgetcsv()、Generator 封装提升复用性、实时响应需禁用缓冲并配置 Web 服务器。
fgets() 逐行读取比 file_get_contents() 更安全大文件或网络流场景下,一次性加载整个文本会爆内存,file_get_contents() 直接失败或拖慢整个请求。用 fgets() 按行读取是 PHP 原生最轻量的流式方案,它每次只从文件指针读取一行(含换行符),内存占用恒定在几百字节级别。
注意点:
fgets() 默认读到 \n 或 EOF 停止,若源数据用 \r\n 或纯 \r 换行,需提前统一或用 stream_set_line_buffer() 调整缓冲行为php://input)且未启用 allow_url_fopen 的环境;此时应改用 php://stdin 或 cURL + CURLOPT_WRITEFUNCTION
fgets() + explode() 粗暴分割——字段内含逗号或换行会导致错切;优先用 fgetcsv()
Generator 封装流式分割逻辑更易复用把逐行处理包装成生成器,能自然支持 foreach 迭代、延迟计算,也避免手动维护文件指针。比如按自定义分隔符(非换行)切分文本块:
function splitByDelimiter($handle, $delimiter = "\n") {
$buffer = '';
while (!feof($handle)) {
$chunk = fread($handle, 8192);
if ($chunk === false) break;
$buffer .= $chunk;
$parts = explode($delimiter, $buffer);
$buffer = array_pop($parts); // 保留不完整末尾
foreach ($parts as $part) {
yield $part;
}
}
if ($buffer !== '') yield $buffer; // 最后一块
}
关键细节:
fread() 大小建议设为 4096–8192 字节,太小增加系统调用开销,太大可能卡住实时流array_pop() 保留在缓冲区是常见做法return 返回值,PHP 7.1+ 支持 yield from,但此处需手动拼接缓冲,不宜直接委托如果后端要边读文件边往前端吐数据(比如日志 tail),光用流式读取不够,PHP 输出层必须配合:
ob_end_flush() 和 flush() 前,确认 output_buffering = Off(php.ini)或运行时调用 ob_implicit_flush(true)
Content-Type 和 Transfer-Encoding: chunked(由 Web 服务器自动加),否则浏览器会等 EOF 才渲染fastcgi_buffering off; 或 proxy_buffering off; 配置才能真正实时fetch() 接流时,得用 response.body.getReader() + read() 循环,不是 response.text()
mb_split() 不适合流式场景,别踩 Unicode 分割坑遇到中文、emoji 等多字节字符时,有人想用 mb_split('/\s+/', $line) 做词粒度切分,但这要求整行已加载进内存,违背流式初衷。更严重的是:mb_split() 在 PHP 8.0+ 已被标记为废弃,且正则引擎对超长 UTF-8 字符串回溯容易触发 PREG_BACKTRACK_LIMIT_ERROR。
替代思路:
substr() 可能截断字符导致乱码IntlBreakIterator,但它必须传入完整字符串,无法增量处理流式处理的核心约束从来不是“能不能切”,而是“切完要不要立刻理解它”。多数真实场景里,先稳住
