PHP readfile() 函数

提示:readfile() 函数用于读取文件并将其写入输出缓冲区。它非常适合用于文件下载、输出图片、CSS、JavaScript等静态文件。此函数是内存高效的,因为它不需要将整个文件加载到内存中。

定义和用法

readfile() 函数用于读取文件并将其写入输出缓冲区。该函数返回从文件中读取的字节数。

与 file_get_contents() 不同,readfile() 不会将整个文件内容读入内存,而是分块读取并直接输出,这使得它非常适合处理大文件。

语法

readfile ( string $filename , bool $use_include_path = false , resource $context = null ) : int|false

参数

参数 类型 说明
filename 字符串 要读取的文件名
use_include_path 布尔值 可选。如果设置为true,会在包含路径(include_path)中搜索文件。默认为false
context 资源 可选。文件句柄的上下文资源。可以通过 stream_context_create() 创建

返回值

成功时返回从文件中读取的字节数,失败时返回 false

示例

示例 1:基本用法 - 读取并输出文本文件

<?php
$filename = 'example.txt';

// 创建一个示例文件
file_put_contents($filename, "这是第一行\n这是第二行\n这是第三行");

// 读取并输出文件内容
$bytes = readfile($filename);

if ($bytes !== false) {
    echo "\n\n成功读取了 {$bytes} 字节的数据";
} else {
    echo "读取文件失败";
}

// 清理
unlink($filename);
?>

示例 2:输出图片文件

<?php
$imageFile = 'image.jpg';

// 检查文件是否存在
if (file_exists($imageFile)) {
    // 设置正确的 Content-Type
    header('Content-Type: image/jpeg');

    // 输出图片
    $bytes = readfile($imageFile);

    if ($bytes === false) {
        // 如果readfile失败,发送404图片
        header("HTTP/1.0 404 Not Found");
        echo "图片文件无法读取";
    }
} else {
    header("HTTP/1.0 404 Not Found");
    echo "图片文件不存在";
}
?>

示例 3:文件下载功能

<?php
// 文件下载函数
function downloadFile($filePath, $displayName = null) {
    // 检查文件是否存在
    if (!file_exists($filePath) || !is_readable($filePath)) {
        header("HTTP/1.0 404 Not Found");
        return false;
    }

    // 获取文件名
    $filename = $displayName ?? basename($filePath);

    // 获取文件大小
    $filesize = filesize($filePath);

    // 设置HTTP头
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . $filesize);

    // 清空输出缓冲区
    if (ob_get_level()) {
        ob_end_clean();
    }

    // 读取并输出文件
    $bytes = readfile($filePath);

    // 返回读取的字节数
    return $bytes;
}

// 使用示例
$file = 'document.pdf';
$result = downloadFile($file);

if ($result !== false) {
    // 下载成功,记录日志等
    error_log("文件 {$file} 被下载,大小: {$result} 字节");
} else {
    echo "文件下载失败";
}
?>

示例 4:处理大文件 - 分块输出

<?php
// 对于非常大的文件,可以手动分块读取以提高性能
function readLargeFile($filename, $chunkSize = 8192) {
    if (!file_exists($filename) || !is_readable($filename)) {
        return false;
    }

    $handle = fopen($filename, 'rb');
    if ($handle === false) {
        return false;
    }

    $totalBytes = 0;

    // 分块读取并输出
    while (!feof($handle)) {
        $buffer = fread($handle, $chunkSize);
        echo $buffer;
        $totalBytes += strlen($buffer);
    }

    fclose($handle);
    return $totalBytes;
}

// 使用readfile的替代方法
$filename = 'large_video.mp4';

// 设置视频内容类型
header('Content-Type: video/mp4');
header('Content-Length: ' . filesize($filename));

// 方法1:使用readfile(PHP内部会分块处理)
// $bytes = readfile($filename);

// 方法2:使用自定义分块函数
$bytes = readLargeFile($filename, 65536); // 64KB块

if ($bytes !== false) {
    // 记录成功
    error_log("成功输出 {$bytes} 字节的大文件");
}
?>

示例 5:错误处理和使用上下文

<?php
// 创建HTTP上下文,设置超时
$contextOptions = [
    'http' => [
        'timeout' => 10, // 10秒超时
        'header' => "User-Agent: MyPHPApp/1.0\r\n"
    ]
];

$context = stream_context_create($contextOptions);

$files = [
    'local_file.txt',
    'nonexistent.txt',
    'https://example.com/robots.txt' // 远程文件
];

foreach ($files as $file) {
    echo "尝试读取文件: $file\n";

    // 使用@抑制警告,手动处理错误
    $bytes = @readfile($file, false, $context);

    if ($bytes === false) {
        $error = error_get_last();
        echo "读取失败: " . ($error['message'] ?? '未知错误') . "\n";
    } else {
        echo "成功读取 {$bytes} 字节\n";
    }

    echo str_repeat('-', 50) . "\n";
}
?>

readfile() 的工作流程

  1. 打开指定的文件
  2. 从文件读取数据(分块读取,通常为8KB)
  3. 将读取的数据直接发送到输出缓冲区
  4. 重复步骤2-3直到文件结束
  5. 关闭文件句柄
  6. 返回读取的总字节数

注意事项

  • 输出缓冲:如果启用了输出缓冲,readfile()的输出可能会被缓冲。使用ob_clean()或ob_end_flush()处理
  • 内存效率:readfile()是内存高效的,适合处理大文件,因为它不会将整个文件加载到内存
  • HTTP头:在输出文件内容前,确保设置正确的Content-Type头
  • 文件权限:确保PHP有读取文件的权限
  • 路径安全:不要直接将用户输入作为文件名,防止路径遍历攻击
  • 远程文件:读取远程文件需要在php.ini中启用allow_url_fopen
  • 性能:对于非常大的文件,可以调整输出缓冲块大小以提高性能
  • 错误处理:使用@抑制错误或检查返回值,提供友好的错误信息

与相关函数的比较

函数 内存使用 返回值 适用场景
readfile() 低(分块) 字节数/false 文件下载、输出静态资源、大文件处理
file_get_contents() 高(全部) 文件内容/false 读取配置文件、小文件处理、需要操作内容时
fread() 可控 字符串/false 需要精细控制读取过程的场景
file() 高(全部) 数组/false 需要按行处理文件内容时

常见应用场景

  1. 文件下载:提供文件下载功能,如PDF、ZIP等
  2. 静态资源:输出图片、CSS、JavaScript等静态文件
  3. 媒体文件:流式传输视频、音频文件
  4. 日志查看:在Web界面查看日志文件
  5. 模板输出:输出HTML模板文件
  6. API响应:输出JSON、XML等数据文件

最佳实践

  1. 设置正确的HTTP头:在调用readfile()前设置Content-Type、Content-Length等头信息
  2. 错误处理:始终检查readfile()的返回值,处理文件不存在或不可读的情况
  3. 安全验证:验证文件路径,防止路径遍历攻击
  4. 内存管理:对于大文件,确保脚本执行时间足够(使用set_time_limit(0))
  5. 输出缓冲:必要时清除输出缓冲区,确保文件正确输出
  6. 记录日志:记录文件访问,便于监控和审计

相关函数