PHP fread()函数

fread()函数用于从打开的文件中读取指定长度的数据,支持二进制安全读取。

语法

string|false fread ( resource $handle , int $length )

参数说明

参数 描述
handle 文件指针资源,通常由fopen()函数创建
length 要读取的最大字节数

返回值

  • 成功时返回读取的字符串
  • 到达文件末尾时返回空字符串
  • 失败时返回false

示例代码

示例1:基本文件读取

<?php
// 创建测试文件
file_put_contents('test.txt', 'Hello, World! This is a test file.');

// 以只读模式打开文件
$handle = fopen('test.txt', 'r');
if ($handle === false) {
    die('无法打开文件');
}

// 读取前5个字节
$content = fread($handle, 5);
echo "前5个字节: " . htmlspecialchars($content) . "<br>";

// 继续读取10个字节
$content = fread($handle, 10);
echo "接下来的10个字节: " . htmlspecialchars($content) . "<br>";

// 读取剩余所有内容
$remaining = fread($handle, filesize('test.txt'));
echo "剩余内容: " . htmlspecialchars($remaining) . "<br>";

fclose($handle);
?>

示例2:读取整个文件

<?php
// 方法1:使用fread()读取整个文件
function readEntireFile_fread($filename) {
    $handle = fopen($filename, 'r');
    if (!$handle) return false;

    // 获取文件大小
    $filesize = filesize($filename);
    if ($filesize === 0) {
        fclose($handle);
        return '';
    }

    // 读取整个文件
    $content = fread($handle, $filesize);
    fclose($handle);

    return $content;
}

// 方法2:使用file_get_contents()读取整个文件
function readEntireFile_filegetcontents($filename) {
    return file_get_contents($filename);
}

// 测试两种方法
$filename = 'example.txt';
file_put_contents($filename, str_repeat('A', 1000)); // 创建1KB的测试文件

$start1 = microtime(true);
$content1 = readEntireFile_fread($filename);
$time1 = microtime(true) - $start1;

$start2 = microtime(true);
$content2 = readEntireFile_filegetcontents($filename);
$time2 = microtime(true) - $start2;

echo "fread()方法读取时间: " . number_format($time1, 6) . " 秒<br>";
echo "file_get_contents()方法读取时间: " . number_format($time2, 6) . " 秒<br>";
echo "两种方法读取内容是否相同: " . ($content1 === $content2 ? '是' : '否') . "<br>";

// 清理测试文件
unlink($filename);
?>

示例3:二进制文件读取

<?php
// 创建二进制文件
$binaryData = pack('C*',
    0x48, 0x65, 0x6C, 0x6C, 0x6F, // Hello
    0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64 // World
);
file_put_contents('binary.dat', $binaryData);

// 以二进制模式读取
$handle = fopen('binary.dat', 'rb'); // 注意:'rb'表示二进制只读模式
if ($handle) {
    // 读取整个文件
    $content = fread($handle, filesize('binary.dat'));
    fclose($handle);

    // 显示二进制内容
    echo "二进制文件内容(十六进制): ";
    for ($i = 0; $i < strlen($content); $i++) {
        echo sprintf('%02X ', ord($content[$i]));
    }
    echo "<br>";

    echo "转换为字符串: " . htmlspecialchars($content) . "<br>";

    // 读取特定字节
    $handle = fopen('binary.dat', 'rb');
    $first5 = fread($handle, 5);
    echo "前5个字节: " . htmlspecialchars($first5) . "<br>";
    fclose($handle);
}

// 读取图片文件的二进制头
function getImageHeader($filename) {
    if (!file_exists($filename)) {
        return false;
    }

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

    // 读取前4个字节(足够识别大多数图片格式)
    $header = fread($handle, 4);
    fclose($handle);

    return $header;
}

// 测试读取图片头
$header = getImageHeader('example.jpg');
if ($header !== false) {
    echo "<br>图片文件头: ";
    for ($i = 0; $i < strlen($header); $i++) {
        echo sprintf('%02X ', ord($header[$i]));
    }
}
?>

示例4:分块读取大文件

<?php
// 高效读取大文件的函数
function readLargeFile($filename, $chunkSize = 8192) {
    $handle = fopen($filename, 'r');
    if (!$handle) {
        throw new Exception("无法打开文件: $filename");
    }

    // 使用生成器,避免一次性加载整个文件到内存
    while (!feof($handle)) {
        $chunk = fread($handle, $chunkSize);
        if ($chunk !== false) {
            yield $chunk;
        }
    }

    fclose($handle);
}

// 使用示例:统计大文件中的单词数量
function countWordsInLargeFile($filename) {
    $wordCount = 0;
    $buffer = '';

    foreach (readLargeFile($filename, 4096) as $chunk) {
        $buffer .= $chunk;

        // 处理缓冲区中的完整单词
        $lastSpace = strrpos($buffer, ' ');
        if ($lastSpace !== false) {
            $toProcess = substr($buffer, 0, $lastSpace + 1);
            $wordCount += str_word_count($toProcess);
            $buffer = substr($buffer, $lastSpace + 1);
        }
    }

    // 处理剩余的缓冲区内容
    if (!empty($buffer)) {
        $wordCount += str_word_count($buffer);
    }

    return $wordCount;
}

// 创建大测试文件
$largeFile = 'large_file.txt';
if (!file_exists($largeFile)) {
    $handle = fopen($largeFile, 'w');
    for ($i = 0; $i < 10000; $i++) {
        fwrite($handle, "This is line $i with some words. ");
    }
    fclose($handle);
}

// 统计单词数量
$count = countWordsInLargeFile($largeFile);
echo "大文件中的单词数量: $count<br>";
echo "文件大小: " . number_format(filesize($largeFile)) . " 字节<br>";

// 清理测试文件(可选)
// unlink($largeFile);
?>

示例5:安全的文件读取器类

<?php
class SafeFileReader {
    private $handle = null;
    private $filename;
    private $mode;
    private $position = 0;

    /**
     * 打开文件
     */
    public function open($filename, $mode = 'r') {
        // 验证文件名安全性
        if (!$this->validateFilename($filename)) {
            throw new Exception("无效或危险的文件名");
        }

        // 验证文件存在性
        if (!file_exists($filename)) {
            throw new Exception("文件不存在: $filename");
        }

        $this->filename = $filename;
        $this->mode = $mode;

        $this->handle = fopen($filename, $mode);
        if (!$this->handle) {
            throw new Exception("无法打开文件: $filename");
        }

        return $this;
    }

    /**
     * 读取指定长度的数据
     */
    public function read($length) {
        if (!$this->handle) {
            throw new Exception("文件未打开");
        }

        $data = fread($this->handle, $length);
        if ($data !== false) {
            $this->position += strlen($data);
        }

        return $data;
    }

    /**
     * 读取一行
     */
    public function readLine() {
        if (!$this->handle) {
            throw new Exception("文件未打开");
        }

        $line = fgets($this->handle);
        if ($line !== false) {
            $this->position += strlen($line);
        }

        return $line;
    }

    /**
     * 读取所有内容
     */
    public function readAll() {
        if (!$this->handle) {
            throw new Exception("文件未打开");
        }

        // 保存当前位置
        $currentPos = ftell($this->handle);

        // 移动到文件开头
        rewind($this->handle);

        // 读取所有内容
        $content = fread($this->handle, filesize($this->filename));

        // 恢复位置
        fseek($this->handle, $currentPos);

        return $content;
    }

    /**
     * 设置读取位置
     */
    public function seek($offset, $whence = SEEK_SET) {
        if (!$this->handle) {
            throw new Exception("文件未打开");
        }

        $result = fseek($this->handle, $offset, $whence);
        if ($result === 0) {
            $this->position = ftell($this->handle);
        }

        return $result;
    }

    /**
     * 获取当前位置
     */
    public function getPosition() {
        return $this->position;
    }

    /**
     * 获取文件大小
     */
    public function getFileSize() {
        return file_exists($this->filename) ? filesize($this->filename) : 0;
    }

    /**
     * 关闭文件
     */
    public function close() {
        if ($this->handle) {
            fclose($this->handle);
            $this->handle = null;
        }
        return $this;
    }

    /**
     * 验证文件名安全性
     */
    private function validateFilename($filename) {
        // 防止目录遍历攻击
        if (strpos($filename, '../') !== false || strpos($filename, '..\\') !== false) {
            return false;
        }

        // 防止空字节攻击
        if (strpos($filename, "\0") !== false) {
            return false;
        }

        // 限制文件类型(可选)
        $allowedExtensions = ['txt', 'log', 'csv', 'json', 'xml'];
        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        if (!in_array($ext, $allowedExtensions)) {
            return false;
        }

        return true;
    }

    public function __destruct() {
        $this->close();
    }
}

// 使用示例
try {
    // 创建测试文件
    file_put_contents('test_data.txt', "Line 1\nLine 2\nLine 3\n");

    // 使用SafeFileReader
    $reader = new SafeFileReader();
    $reader->open('test_data.txt');

    echo "文件大小: " . $reader->getFileSize() . " 字节<br>";

    // 读取第一行
    echo "第一行: " . htmlspecialchars($reader->readLine()) . "<br>";

    // 读取接下来的10个字节
    echo "接下来10字节: " . htmlspecialchars($reader->read(10)) . "<br>";

    // 获取当前位置
    echo "当前位置: " . $reader->getPosition() . "<br>";

    // 读取剩余所有内容
    echo "剩余内容: " . htmlspecialchars($reader->readAll()) . "<br>";

    $reader->close();

} catch (Exception $e) {
    echo "错误: " . $e->getMessage();
}
?>

注意事项

重要提示:
  • 二进制安全:fread()是二进制安全的,可以读取包含空字符(\0)的数据
  • 长度参数:length参数指定最大读取字节数,实际读取的字节数可能小于此值
  • 文件指针:读取后文件指针会移动到读取内容的末尾
  • 大文件处理:读取大文件时,应使用分块读取避免内存溢出
  • 网络流:从网络流读取时,fread()可能不会一次性读取请求的所有数据
  • 性能考虑:对于小文件,使用file_get_contents()通常更方便
  • 文件锁定:多进程读取同一文件时,考虑使用flock()进行文件锁定
  • 编码问题:读取文本文件时,注意文件的字符编码,可能需要使用mb_convert_encoding()转换

fread() vs file_get_contents()

特性 fread() file_get_contents()
使用方式 需要先打开文件句柄 直接传入文件名
控制粒度 可以精确控制读取位置和长度 只能读取整个文件
内存使用 可以分块读取,适合大文件 一次性加载整个文件到内存
性能 对于大文件更优(分块读取) 对于小文件更优(单次调用)
适用场景 大文件处理、二进制文件、需要精确控制时 小文件读取、配置文件、简单文本文件

常见应用场景

配置文件读取

读取INI、JSON、XML等配置文件,可以按需读取特定部分。

二进制文件处理

处理图片、音频、视频等二进制文件,读取文件头或特定数据块。

日志文件分析

分析大型日志文件,分块读取避免内存溢出。

文件上传处理

处理用户上传的文件,验证文件类型和内容。

相关函数

  • fopen() - 打开文件或URL
  • fclose() - 关闭一个已打开的文件指针
  • fwrite() - 写入文件(fputs是其别名)
  • fgets() - 从文件指针中读取一行
  • feof() - 测试文件指针是否到达文件末尾
  • fseek() - 在文件指针中定位
  • ftell() - 返回文件指针读/写的位置
  • file_get_contents() - 将整个文件读入字符串