最稳妥方式是用 fopen + fgets 逐行读取并控制循环次数,注意 trim 去换行符、false 判断防死循环、rewind 重置指针,同时校验并转换文件编码以防乱码。

用 fopen + fgets 逐行读取最稳妥
直接读整个文件再 explode("\n") 容易爆内存,尤其大文件。真实场景里,你通常只关心前 10 行日志或配置头,没必要加载全部内容。
关键点是控制循环次数,同时注意换行符兼容性(\n、\r\n 都可能):
-
fgets自动识别不同平台的换行,比file()更轻量 - 必须用
trim($line)去掉末尾换行和空格,否则判断空行会出错 - 遇到
false(EOF 或读取失败)要立即break,否则可能死循环
file()
-
file()返回数组,每行一个元素,但底层仍是全量读取 -
file_get_contents()+substr()更危险:二进制截断可能切在 UTF-8 多字节中间,导致乱码 - 如果真要用字符串截断,至少先用
mb_strcut($content, 0, $pos, 'UTF-8')
用 SplFileObject 更面向对象,但要注意构造参数
它封装了底层指针操作,适合需要多次随机访问行号的场景,比如“跳过前 3 行再读 5 行”。但默认不跳过空行或 BOM,容易误判行数。
- 构造时传
SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE才能干净读取 - 调用
$obj->seek(0)不等于重置文件指针到开头,得重新 new 一个实例或用rewind() - 性能略低于原生
fgets,因为多了对象开销,小文件无感,高频调用需实测
$obj = new SplFileObject('/path/to/config.ini');
$obj->setFlags(SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE);
$lines = [];
for ($i = 0; $i valid(); $i++, $obj->next()) {
$lines[] = $obj->current();
}
Windows 下路径和编码最容易踩坑
不是所有服务器都用 UTF-8。特别是 Windows 上 PHP 默认用 ANSI(通常是 GBK),而日志文件可能是 UTF-8 带 BOM,fgets 读出来第一行开头就多出 \xEF\xBB\xBF,看着像乱码。
- 用
mb_detect_encoding($line, ['UTF-8', 'GBK'], true)判断后再转码 - 绝对路径推荐用
__DIR__ . '/data/file.txt',避免./在 CLI 和 Web 环境下工作目录不同 - 打开文件前加
if (!is_readable($path)) { throw new RuntimeException("Cannot read $path"); },别让fopen静默失败
实际写的时候,多数情况用第一个方案就够了;但只要涉及中文路径、老旧系统或不确定编码,就得在读取后立刻做编码校验——这点很多人等到线上报错才想起来。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











