根本原因是默认缓冲区太小导致频繁系统调用;建议普通磁盘文件用64kb起步测试,ssd可试256kb,需结合内存约束与实际测量动态调整。

为什么 fread() 读大文件特别慢?
根本原因不是 fread() 本身慢,而是默认缓冲区太小(通常 8192 字节),导致频繁系统调用。每次 fread($fp, $length) 都可能触发一次 read() 系统调用,尤其当 $length 远小于文件实际可读字节数时,PHP 会反复试探、拷贝、返回,I/O 开销被放大。
典型表现:fread($fp, 8192) 读一个 100MB 文件,可能触发上万次系统调用;而换成一次读 64KB,调用次数降到约 1600 次。
- 不是所有场景都适用大缓冲——内存受限或流式处理时,盲目加大反而拖慢响应
-
fread()的第二个参数是“最多读多少”,不等于“保证读满”,它受底层缓冲、文件末尾、非阻塞模式等影响 - 注意:PHP 的
stream_set_read_buffer()对fread()无效,它只影响fgets()、fgetss()等行读函数
怎么设合理的 fread() 缓冲长度?
没有固定值,但有可操作的参考策略:
- 普通磁盘文件(非网络流):从
65536(64KB)起步测试,多数情况比默认8192快 2–5 倍 - SSD 或高 IOPS 存储:可试
262144(256KB),但超过 1MB 一般收益递减,还可能触发 GC 压力 - 配合
stream_get_meta_data($fp)['unread_bytes']判断剩余可读量,动态调整下一次fread()长度(避免末尾小读) - 如果文件大小已知(如
filesize()),可用min(262144, $remaining)控制单次上限
示例:
立即学习“PHP免费学习笔记(深入)”;
$fp = fopen('/path/to/big.log', 'rb');
$buffer_size = 65536;
while (!feof($fp)) {
$chunk = fread($fp, $buffer_size);
if ($chunk === false) break;
// 处理 $chunk
}
fclose($fp);
fread() vs file_get_contents() 性能怎么选?
关键看使用场景,不是“谁更快”而是“谁更合适”:
-
file_get_contents()内部用mmap(Linux)或高效批量读,对中小文件( - 但它是“全量加载到内存”,读 1GB 文件会直接 OOM;
fread()流式读可控制峰值内存 -
file_get_contents()不支持断点续读、边读边解析(如解析 CSV 行)、或读取不支持stat()的流(如php://input) - 若必须用
fread(),别忽略stream_set_chunk_size($fp, $size)—— 它影响底层read()的预读行为,和fread()缓冲协同生效(PHP ≥ 7.2.22 / 7.3.9)
容易被忽略的陷阱:fread 读网络流或压缩流时别硬套磁盘经验
本地文件优化那套逻辑,在 ftp://、http://、compress.zlib:// 上可能适得其反:
- 网络流的延迟远高于磁盘,过大的
fread()长度会导致单次等待时间拉长,卡住整个流程 - zlib 流内部已有解压缓冲,再设大
fread()可能引发重复解压或内存碎片 - 用
stream_get_wrappers()检查流类型,对http、https、php://memory等,建议保持8192–32768区间并加超时控制 - 调试时用
strace -e trace=read php script.php 2>&1 | head -20(Linux)直观看系统调用次数和每次读长,比猜更准
缓冲区不是越大越好,真正影响性能的是「单位数据的系统调用开销」和「你的内存/延迟约束」之间的平衡点。测,别估。











