不能用file()或file_get_contents()读大文件,因其会一次性将整个文件加载到内存,1GB文件可能占用1.2GB内存并触发OOM;应改用fopen+feof+fgets流式逐行读取,保持单行在内存中、读完即弃,注意编码、行尾兼容性及显式fclose释放资源。

为什么不能用 file() 或 file_get_contents() 读大文件
因为 file() 会把整份文件加载进内存,哪怕只是匹配一行关键字,1GB 文件就可能吃掉 1.2GB 内存(PHP 字符串开销),直接触发 Allowed memory size exhausted。而 file_get_contents() 更危险——它连换行分割都不做,你得自己 explode("\n", ...),内存占用更高、还容易切错多字节字符。
用 fopen + fgets 流式逐行读取的正确姿势
核心是保持单行在内存中,读完即弃。注意三点:编码一致性、行尾兼容性、资源释放。
-
fgets()默认读到\n或 EOF,但 Windows 是\r\n,PHP 会自动处理,无需额外 trim - 如果文件是 UTF-8 且含中文,确保 PHP 脚本本身也声明为 UTF-8(
mb_internal_encoding('UTF-8')可选,但preg_match()默认按字节匹配,中文需加u修饰符) - 必须用
fclose($fp)显式关闭句柄,否则可能触发Too many open files
$fp = fopen('/path/to/big.log', 'r');
if (!$fp) {
die('无法打开文件');
}
while (($line = fgets($fp)) !== false) {
if (preg_match('/error|timeout/i', $line)) {
echo $line;
}
}
fclose($fp);
preg_match 性能与安全细节:什么时候该用 preg_match_all?
只查“是否出现”用 preg_match();要提取所有匹配项或捕获组才用 preg_match_all()。对大文件来说,后者会构建完整匹配数组,内存增长不可控。
- 避免写
preg_match('/.*keyword.*/i', $line)——.*引发回溯,尤其长行时变慢甚至卡死 - 简单关键字优先用
stripos($line, 'keyword') !== false,比preg_match快 3–5 倍,且无正则风险 - 真要用正则,模式尽量具体:比如匹配日志中的 IP,写
/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/比/(?:\d+\.){3}\d+/更安全
遇到二进制内容、空行或超长行怎么办
fgets() 在默认 1024 字节缓冲下,遇到超长行会截断——这不是 bug,是设计行为。若文件含无换行的巨型 JSON 或 base64,需手动扩容:
立即学习“PHP免费学习笔记(深入)”;
// 设置更大缓冲区(如 8KB)
while (($line = fgets($fp, 8192)) !== false) {
// 处理
}
若文件混有二进制数据(比如 logrotate 后的压缩段),fgets() 可能提前终止于 \x00。此时应改用 fread($fp, 8192) 配合 strtok() 手动按 \n 切分,但要注意跨块换行丢失问题——这种场景已超出“按行搜索”范畴,建议先解压或过滤。
真正容易被忽略的是:某些日志框架会在行首插入 ANSI 颜色码(如 \x1b[31mERROR\x1b[0m),preg_match 不受影响,但人眼难识别。调试时可用 bin2hex($line) 快速确认。



















