← PHP explode()函数:字符串分割与数组转换 PHP get_html_translation_table()函数详解:翻译表、编码标志与避坑指南 →

PHP fprintf()函数:格式化字符串写入流指南

著
原创 2026-10-10 PHP 已有人查阅

1、认识fprintf():不只是printf()的文件版本

PHP里做格式化输出,很多人第一反应是printf()和sprintf()。但当你需要把格式化后的内容直接写进文件、套接字或者数据库句柄时,fprintf()才是更合适的选择。

它的本质是:把格式化字符串写入一个已经打开的流资源。这个流可以是本地文件、远程URL、php://output,也可以是某些数据库扩展提供的流式句柄。

语法结构:

fprintf(stream, format, arg1, arg2, arg++)
参数 说明 是否必填
stream 目标流资源,通常由fopen()返回 必填
format 格式化模板字符串 必填
arg1 第一个插入参数 必填
arg2 第二个插入参数 可选
arg++ 后续插入参数 可选

返回值为写入字符串的字节长度。这一点在项目里经常被忽略,但它可以用来判断写入是否成功、计算偏移量,或者做简单的写入量统计。

2、fprintf()与printf()、sprintf()的取舍

这三个函数经常被混用,实际项目里要分清楚:

  • printf():直接输出到标准输出(浏览器、终端),适合调试和CLI脚本。

  • sprintf():返回格式化后的字符串,不直接输出,适合拼接后二次处理。

  • fprintf():写入指定流,适合日志文件、导出文件、流式响应。

个人经验:做日志系统时,早期我用sprintf()拼好字符串再fwrite(),多了一步内存拷贝。后来改成fprintf()直接写入文件句柄,代码更短,语义也更清晰。但要注意,fprintf()不适合频繁写入大量小片段,因为每次调用都有格式化开销,批量场景建议先拼再写。

3、格式占位符全解析

fprintf()的格式化规则继承自C语言风格,PHP在此基础上做了一些调整。下面按类型说明。

整数类

$num1 = 123456789;
$num2 = -123456789;

fprintf($file, "%%b = %b\n", $num1); // 二进制
fprintf($file, "%%c = %c\n", 50);    // ASCII 字符
fprintf($file, "%%d = %d\n", $num1); // 有符号十进制
fprintf($file, "%%u = %u\n", $num2); // 无符号十进制
fprintf($file, "%%o = %o\n", $num1); // 八进制
fprintf($file, "%%x = %x\n", $num1); // 十六进制小写
fprintf($file, "%%X = %X\n", $num1); // 十六进制大写

这里有一个容易踩的坑:%u在64位PHP上对负数会得到一个很大的正数,因为底层按无符号解释。上面的$num2=-123456789输出为4171510507,这不是bug,而是补码表示的结果。如果你需要跨平台一致的负数处理,不要依赖%u。

浮点类

fprintf($file, "%%e = %e\n", $num1); // 科学计数法小写
fprintf($file, "%%E = %E\n", $num1); // 科学计数法大写
fprintf($file, "%%f = %f\n", $num1); // 浮点数,本地设置感知
fprintf($file, "%%F = %F\n", $num1); // 浮点数,非本地设置感知
fprintf($file, "%%g = %g\n", $num1); // %e 和 %f 中较短者
fprintf($file, "%%G = %G\n", $num1); // %E 和 %f 中较短者

%f和%F的区别在于小数点是否受locale影响。做多语言项目时,如果服务器locale设置了逗号作为小数点,%f可能输出123456789,000000,而%F始终输出点号。涉及金额、报表导出时,建议统一用%F,避免下游解析出错。

字符串与符号

fprintf($file, "%%s = %s\n", $num1);  // 字符串
fprintf($file, "%%+d = %+d\n", $num1); // 强制带符号
fprintf($file, "%%+d = %+d\n", $num2);

%+d会强制显示正负号,适合做数据对比表、财务流水导出。

4、宽度、精度与填充:实际项目里真正有用的部分

官方文档对这部分讲得比较散,但项目里很常用。格式模板支持:

%[符号][宽度][.精度][类型]

示例:

$file = fopen("report.txt", "w");

// 右对齐,宽度 10,左侧补空格
fprintf($file, "%10s|%10s\n", "编号", "金额");

// 左侧补零
fprintf($file, "%05d\n", 42); // 00042

// 浮点保留两位小数
fprintf($file, "%.2F\n", 3.14159); // 3.14

// 左对齐
fprintf($file, "%-10s|\n", "代码号");

fclose($file);

做对账单导出时,我用%010.2F统一金额格式,保证每行长度一致,后续用固定宽度解析不会错位。这是sprintf()也能做,但fprintf()直接写文件更省事。

5、项目实例:用fprintf()写一个结构化日志

下面是一个简化但可用的日志写入示例,结合了时间戳、级别、格式化对齐。

<?php
$logFile = fopen("app.log", "a");

$level = "INFO";
$userId = 1024;
$action = "login";
$ip = "192.168.1.10";

fprintf(
    $logFile,
    "[%s] %-5s uid=%d action=%-8s ip=%s\n",
    date("Y-m-d"),
    $level,
    $userId,
    $action,
    $ip
);

fclose($logFile);

写入结果:

[2026-10-10] INFO  uid=1024 action=login    ip=192.168.1.10

这里%-5s和%-8s保证不同级别的日志对齐,方便肉眼扫读,也方便后续用正则解析。如果是ERROR级别,可以换成%s不加宽度,或者统一宽度让所有行等长。

6、返回值处理与错误排查

fprintf()返回写入的字节数。如果返回false,说明写入失败。常见原因:

  1. 流未以可写模式打开,比如fopen($file,"r")。

  2. 磁盘空间不足或权限不够。

  3. 流已被关闭。

  4. 格式化参数数量少于占位符数量,PHP8会抛出ArgumentCountError。

个人踩坑经历:有一次在循环里反复fopen()同一个文件但忘记fclose(),导致文件句柄泄漏,最终fprintf()返回false。后来改成打开一次、循环内多次fprintf()、之后统一关闭。这个模式在写导出文件时更稳妥。

fprintf()不会自动加换行,需要自己在格式串里写\n或PHP_EOL。跨平台项目建议用PHP_EOL。

7、本节课程知识要点

  • fprintf()写入的是流资源,不是字符串返回值。

  • 返回值是字节数,可用于写入校验。

  • %u对负数在不同平台表现不同,谨慎使用。

  • %f受locale影响,%F不受影响,金额场景优先%F。

  • 宽度、精度、填充符可以组合使用,适合报表类输出。

  • 打开一次、多次写入、之后关闭,是更安全的文件写入模式。

8、与数据库写入的关系

文章开头提到“写入文件或数据库”。严格来说,fprintf()本身不直接操作数据库,它操作的是流。某些数据库扩展或ORM提供流式接口时,才可能把流句柄交给fprintf()。更常见的做法是:用fprintf()生成格式化的SQL片段或CSV文件,再导入数据库。理解这一点,可以避免在数据库场景下误用fprintf()。

9格式占位符速查

占位符 含义
%b 二进制
%c ASCII字符
%d 有符号十进制
%u 无符号十进制
%e/%E 科学计数法
%f/%F 浮点数
%g/%G 较短浮点表示
%o 八进制
%s 字符串
%x/%X 十六进制
%% 百分号本身

fprintf()在PHP里不是最显眼的函数,但在日志、导出、流式响应这些场景里,它比sprintf()+fwrite()更直接。掌握格式占位符的细节,尤其是%u、%f和宽度填充的行为,能减少不少调试时间。项目里建议把常用的日志格式封装成函数,内部用fprintf()写入,既保持格式统一,也方便后续切换输出目标。

← PHP explode()函数:字符串分割与数组转换 PHP get_html_translation_table()函数详解:翻译表、编码标志与避坑指南 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号