php读二进制文件必须用'rb'模式,否则windows下\r\n转换会损坏数据;大文件需fread分块读取并校验返回值;unpack解析须严格匹配字节序、长度与对齐;非utf-8数据禁用mb_*和iconv;json_encode对\x00截断无警告;stream_copy_to_stream需检查返回值及流状态。

用 fopen() + fread() 读二进制文件必须加 'rb' 模式
PHP 默认文本模式会把 \r\n 转成 \n,Windows 下读取图片、音频、加密数据时直接损坏内容。不加 'b' 标志,fread() 返回的字节流就不可信。
实操建议:
-
fopen($path, 'rb')是底线,哪怕在 Linux 也得写,否则跨平台代码一跑就错 - 不要用
file_get_contents($path)直接读大文件——内存爆掉前你都收不到错误,只看到进程被 kill - 读取超过几 MB 的文件,优先用
fread()分块(比如每次 8192 字节),并检查返回值是否为false或 0(EOF 或出错)
unpack() 解析二进制结构前必须确认字节序和数据对齐
从文件里读出一串原始字节后,想当整数、浮点数或结构体用,unpack() 是最常用入口。但它不猜格式:你写 'N' 就按大端无符号 32 位解,写 'V' 就小端;字段长度、填充、偏移全靠你自己算准。
常见错误现象:
立即学习“PHP免费学习笔记(深入)”;
- 读出来的数字是负数或巨大乱值 → 实际是字节序反了,比如文件存的是小端但用了
'N' - 后续字段全部错位 → 前一个字段没按真实长度读完,或者结构里有 padding(如 C struct 的 4 字节对齐)没跳过
-
unpack()返回空数组 → 格式字符串长度和输入字节数不匹配,比如用'S'(2 字节)去解 3 字节片段
示例:读一个 Windows BMP 文件头前 14 字节,其中 offset 12 开始是 4 字节像素数据起始偏移:
/* 假设 $header 是 fread($fp, 14) 的结果 */
$parts = unpack('A2signature/Vfilesize/Vreserved/Vdataoffset', $header);
// 'A2' 提取前两个字符,'V' 确保小端解析 —— BMP 是小端格式处理非 UTF-8 二进制数据时,mb_* 和 iconv() 全部失效
一旦数据不是文本编码(比如 JPEG 的 APP0 段、自定义协议包、加密密文),所有多字节字符串函数都会误判边界、截断或报错。PHP 不会自动识别“这是一段 base64 编码过的二进制”,它只认字节流。
关键判断点:
- 只要
ord($byte) > 127出现频繁,就别碰mb_strlen()—— 它可能直接返回false或错计长度 -
json_encode()对含\x00的二进制字符串会静默截断,连 warning 都不抛 - 需要做 base64 编码传输?用
base64_encode($raw_bytes),别先utf8_encode()—— 后者只适合 Latin-1 → UTF-8 转换,对任意二进制是灾难
用 stream_copy_to_stream() 中转大文件时注意资源泄漏风险
比如要把上传的 ZIP 包原样写入 S3 流或加密后再落盘,stream_copy_to_stream($src, $dst) 看似省事,但它内部不校验目标流是否支持 seek 或 write,失败时可能只返回 0,而源流位置已移动。
实操要点:
- 调用前确保
$src和$dst都是有效的、可写的二进制流(用stream_get_meta_data()查mode是否含b) - 务必检查返回值是否等于预期字节数;不等就说明中途出错,此时
$src的指针已偏移,不能简单重试 - 用完立刻
fclose(),尤其在循环中处理多个文件时——PHP 不会自动 gc 流资源,fd 耗尽后fopen()开始返回false,错误信息却是 “Too many open files”
二进制操作里最麻烦的永远不是读或写,而是你根本没法靠肉眼判断哪一步悄悄改了字节顺序、漏了 padding、或者让流指针飘到了奇怪位置。











