不能用 file() 或 file_get_contents() 读大文件,因二者会将整个文件载入内存,易触发内存耗尽错误;应使用 fopen()+fgets()+yield 构建生成器或 splfileobject 迭代器,以恒定低内存按行处理,并注意 bom、换行符兼容性及中断恢复。

为什么不能用 file() 或 file_get_contents() 读大文件
这两个函数会把整个文件内容一次性载入内存,哪怕只是 500MB 的日志,也极可能触发 Fatal error: Allowed memory size of XXX bytes exhausted。PHP 默认 memory_limit 通常只有 128M 或 256M,远不够撑住 GB 级文件。这不是性能问题,是直接崩溃。
用 fopen() + fgets() + yield 构建安全生成器
核心是打开文件句柄后,每次只读一行、立刻 yield、不缓存历史行——内存占用几乎恒定在几 KB 级别。
必须注意的实操细节:
-
fgets()读到换行符(\n或\r\n)就停,所以它天然适合按行处理;但要注意最后一行没换行符时仍能正确返回 - 务必用
try...finally包裹,确保即使中途break或异常,fclose()也会执行,否则句柄泄漏会拖垮后续请求 -
trim($line)很常用,但若原始行含大量空格或制表符,trim()会复制字符串——对超长行要谨慎;可改用rtrim($line, "\r\n")避免全量扫描 - 生成器函数本身不执行,只返回
Generator对象;真正开始读取是在foreach第一次调用current()时
比手写 fopen 更省心:用 SplFileObject 迭代器
它把文件当对象封装,自带 valid()、current()、next() 接口,和生成器一样支持 foreach,且自动处理编码、BOM、行末空白等边界情况。
典型用法:
$file = new SplFileObject('/path/to/big.csv');
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE);
foreach ($file as $row) {
// $row 是已解析的数组,无需再 <code>fgetcsv()</code>
if ($row === false) continue;
// 处理逻辑
}
注意点:
- 构造时若文件不存在或不可读,会直接抛
RuntimeException,需try/catch -
setFlags()中READ_CSV会自动调用fgetcsv(),比手动fgets()+str_getcsv()更健壮 - 它内部仍用
fgets(),所以内存优势和手写生成器一致,但代码更短、容错更强
容易被忽略的陷阱:文件编码、行尾和中断恢复
真实生产环境里,这几个点常导致数据错位或漏读:
- 文件是 UTF-8 with BOM?
SplFileObject默认不跳过 BOM,首行可能带\xEF\xBB\xBF,需手动ltrim($line, "\xEF\xBB\xBF")或用mb_convert_encoding()归一化 - Windows 换行符
\r\n在 Linux 下用fgets()没问题,但若文件混用\r(旧 Mac)或无换行结尾,feof()可能误判——别依赖!feof()做循环条件,以fgets()返回false为准 - 如果处理中途失败(如网络中断、超时),得记录当前行号或字节偏移。生成器本身不保存位置,需额外用
ftell($handle)记录,并在重启时用fseek($handle, $offset)恢复,否则重跑会重复或跳过
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











