PHP fgetc() 函数

说明: fgetc() 函数用于从文件指针中读取单个字符。

语法

string|false fgetc ( resource $handle )

参数说明

参数 描述 必需
handle fopen() 打开的文件指针

返回值

  • 成功时返回包含一个字符的字符串
  • 到达文件末尾(EOF)时返回 FALSE
  • 读取失败时返回 FALSE

注意事项

  • fgetc() 每次只读取一个字节(一个字符),适用于单字节编码(如ASCII)
  • 对于多字节字符(如UTF-8),fgetc() 可能只读取字符的一部分,导致乱码
  • 文件指针必须是有效的,并且指向一个成功打开的文件
  • 读取后文件指针会自动移动到下一个字符
  • 如果文件为空或已到达文件末尾,fgetc() 会返回 false
  • 使用二进制模式("rb")打开文件可以避免换行符转换问题

字符编码说明

不同编码下的字符长度:
编码 英文字符 中文字符 表情符号
ASCII 1字节 不支持 不支持
ISO-8859-1 1字节 不支持 不支持
UTF-8 1字节 3字节 4字节
UTF-16 2字节 2字节 4字节

注意: 由于 fgetc() 每次读取一个字节,在处理多字节编码时需要使用其他方法,如 mb_substr() 或适当的流处理。

示例

示例 1:基本使用 - 逐字符读取文件

<?php
// 创建测试文件
$filename = "test.txt";
file_put_contents($filename, "Hello World!");

// 打开文件
$handle = fopen($filename, "r");
if (!$handle) {
    die("无法打开文件");
}

echo "逐字符读取文件内容:\n";

// 逐字符读取
while (($char = fgetc($handle)) !== false) {
    echo "字符: '" . $char . "'  ASCII: " . ord($char) . "\n";
}

// 关闭文件
fclose($handle);

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

示例 2:统计字符频率

<?php
/**
 * 统计文件中每个字符的出现频率
 */
function count_char_frequency($filename) {
    $handle = fopen($filename, "r");
    if (!$handle) {
        return false;
    }

    $frequency = [];
    $total_chars = 0;

    while (($char = fgetc($handle)) !== false) {
        // 跳过换行符和空格(可选)
        if ($char === "\n" || $char === "\r" || $char === " ") {
            continue;
        }

        if (!isset($frequency[$char])) {
            $frequency[$char] = 0;
        }

        $frequency[$char]++;
        $total_chars++;
    }

    fclose($handle);

    // 按频率降序排序
    arsort($frequency);

    return [
        'frequency' => $frequency,
        'total_chars' => $total_chars,
        'unique_chars' => count($frequency)
    ];
}

// 创建测试文件
$test_file = "char_test.txt";
$content = "This is a test file for character frequency analysis.\n";
$content .= "It contains various characters: a, b, c, 1, 2, 3, !, @, #, etc.\n";
file_put_contents($test_file, $content);

// 统计字符频率
$result = count_char_frequency($test_file);

if ($result) {
    echo "字符频率分析结果:\n";
    echo "====================\n";
    echo "总字符数(排除空格和换行): " . $result['total_chars'] . "\n";
    echo "唯一字符数: " . $result['unique_chars'] . "\n\n";

    echo "字符频率(前10个):\n";
    $count = 0;
    foreach ($result['frequency'] as $char => $freq) {
        if (++$count > 10) break;

        $char_display = ($char === "\t") ? "\\t" :
                       ($char === "\n") ? "\\n" :
                       ($char === "\r") ? "\\r" : $char;

        $percentage = ($freq / $result['total_chars']) * 100;
        echo sprintf("字符 '%s' (ASCII: %3d) : %4d 次 (%5.1f%%)\n",
                     $char_display, ord($char), $freq, $percentage);
    }
}

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

示例 3:UTF-8多字节字符处理

<?php
/**
 * 安全的多字节字符读取类
 * 正确处理UTF-8等多字节编码
 */
class MultibyteCharReader {
    private $handle;
    private $encoding;

    public function __construct($filename, $encoding = 'UTF-8') {
        $this->encoding = $encoding;
        $this->handle = fopen($filename, "rb"); // 二进制模式

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

    /**
     * 读取一个完整的多字节字符
     */
    public function readChar() {
        $first_byte = fgetc($this->handle);

        if ($first_byte === false) {
            return false;
        }

        // 判断UTF-8字符的字节数
        $bytes = 1;
        $ord = ord($first_byte);

        if ($ord < 0x80) {
            // ASCII字符,1字节
            return $first_byte;
        } elseif (($ord & 0xE0) == 0xC0) {
            // 2字节字符
            $bytes = 2;
        } elseif (($ord & 0xF0) == 0xE0) {
            // 3字节字符
            $bytes = 3;
        } elseif (($ord & 0xF8) == 0xF0) {
            // 4字节字符
            $bytes = 4;
        } else {
            // 无效的UTF-8起始字节
            return $first_byte;
        }

        // 读取剩余字节
        $char = $first_byte;
        for ($i = 1; $i < $bytes; $i++) {
            $next_byte = fgetc($this->handle);
            if ($next_byte === false) {
                break;
            }
            $char .= $next_byte;
        }

        return $char;
    }

    /**
     * 读取指定数量的字符
     */
    public function readChars($count) {
        $result = '';
        for ($i = 0; $i < $count; $i++) {
            $char = $this->readChar();
            if ($char === false) {
                break;
            }
            $result .= $char;
        }
        return $result;
    }

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

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

    /**
     * 获取当前文件位置
     */
    public function getPosition() {
        if ($this->handle) {
            return ftell($this->handle);
        }
        return false;
    }
}

// 使用示例
echo "多字节字符读取演示:\n";
echo "===================\n";

// 创建包含多字节字符的测试文件
$utf8_file = "utf8_test.txt";
$utf8_content = "Hello 你好 🌍 World! 测试 🎉 表情符号。\n";
$utf8_content .= "ASCII: ABC abc 123\n";
$utf8_content .= "中文:这是一段测试文本。\n";
$utf8_content .= "混合:Hello 世界!🚀 火箭\n";
file_put_contents($utf8_file, $utf8_content, LOCK_EX);

echo "文件内容预览:\n";
echo $utf8_content . "\n";

echo "逐字符分析:\n";

try {
    $reader = new MultibyteCharReader($utf8_file, 'UTF-8');

    $char_count = 0;
    while (($char = $reader->readChar()) !== false) {
        $char_count++;

        // 跳过换行符以便更好显示
        if ($char === "\n") {
            echo "[换行符]\n";
            continue;
        }

        // 显示字符信息
        $bytes = strlen($char);
        $hex = '';
        for ($i = 0; $i < $bytes; $i++) {
            $hex .= sprintf('%02X ', ord($char[$i]));
        }

        echo sprintf("字符 #%03d: '%s' (字节: %d, 十六进制: %s)\n",
                     $char_count, $char, $bytes, trim($hex));

        // 只显示前30个字符的详细分析
        if ($char_count >= 30) {
            echo "... 只显示前30个字符\n";
            break;
        }
    }

    $reader->close();

    echo "\n字符总数(包括换行符): " . $char_count . "\n";

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

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

示例 4:解析简单文件格式

<?php
/**
 * 简单CSV解析器(使用fgetc逐字符解析)
 * 支持引号和转义字符
 */
class SimpleCsvParser {
    private $handle;
    private $delimiter;
    private $enclosure;
    private $escape;

    public function __construct($filename, $delimiter = ',', $enclosure = '"', $escape = '\\') {
        $this->delimiter = $delimiter;
        $this->enclosure = $enclosure;
        $this->escape = $escape;
        $this->handle = fopen($filename, "r");

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

    /**
     * 读取一行CSV数据
     */
    public function readRow() {
        if (feof($this->handle)) {
            return false;
        }

        $row = [];
        $field = '';
        $in_quotes = false;
        $prev_char = '';

        while (($char = fgetc($this->handle)) !== false) {
            // 处理转义字符
            if ($prev_char === $this->escape) {
                $field .= $char;
                $prev_char = '';
                continue;
            }

            // 处理引号
            if ($char === $this->enclosure) {
                if ($in_quotes) {
                    // 查看下一个字符是否是另一个引号(转义引号)
                    $next_char = fgetc($this->handle);
                    if ($next_char === $this->enclosure) {
                        $field .= $char;
                        continue;
                    } else {
                        // 将指针移回
                        fseek($this->handle, -1, SEEK_CUR);
                        $in_quotes = false;
                    }
                } else {
                    $in_quotes = true;
                }
                $prev_char = $char;
                continue;
            }

            // 处理分隔符
            if ($char === $this->delimiter && !$in_quotes) {
                $row[] = $field;
                $field = '';
                $prev_char = $char;
                continue;
            }

            // 处理行结束
            if ($char === "\n" && !$in_quotes) {
                $row[] = $field;
                return $row;
            }

            if ($char === "\r") {
                // 跳过回车符,等待换行符
                continue;
            }

            // 添加字符到字段
            $field .= $char;
            $prev_char = $char;
        }

        // 文件结束,返回最后一个字段
        if ($field !== '' || !empty($row)) {
            $row[] = $field;
            return $row;
        }

        return false;
    }

    /**
     * 读取所有数据
     */
    public function readAll() {
        $data = [];
        while (($row = $this->readRow()) !== false) {
            $data[] = $row;
        }
        return $data;
    }

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

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

// 使用示例
echo "简单CSV解析器演示:\n";
echo "==================\n";

// 创建测试CSV文件
$csv_content = 'Name,Age,City,Description
John Doe,25,"New York, USA","Software Engineer"
Jane Smith,30,"Los Angeles","Data Analyst, \"Expert\""
Bob Johnson,35,"Chicago","Manager
Handles multiple teams"
Alice Brown,28,"Miami","Marketing Specialist"';

$csv_file = "test.csv";
file_put_contents($csv_file, $csv_content);

try {
    $parser = new SimpleCsvParser($csv_file);

    echo "解析CSV文件内容:\n";
    echo "原始内容:\n";
    echo $csv_content . "\n\n";

    echo "解析结果:\n";
    $data = $parser->readAll();

    foreach ($data as $row_num => $row) {
        echo "行 " . ($row_num + 1) . ": ";
        foreach ($row as $col_num => $cell) {
            echo "[$col_num: \"" . $cell . "\"] ";
        }
        echo "\n";
    }

    $parser->close();

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

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

示例 5:文本分析工具

<?php
/**
 * 文本分析工具类
 * 使用fgetc()进行详细的文本分析
 */
class TextAnalyzer {
    private $handle;
    private $filename;

    public function __construct($filename) {
        $this->filename = $filename;
        $this->handle = fopen($filename, "r");

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

    /**
     * 统计基本文本信息
     */
    public function analyzeBasic() {
        rewind($this->handle);

        $stats = [
            'chars' => 0,
            'letters' => 0,
            'digits' => 0,
            'spaces' => 0,
            'punctuation' => 0,
            'lines' => 1, // 从1开始计数
            'words' => 0,
            'in_word' => false
        ];

        $prev_char = '';

        while (($char = fgetc($this->handle)) !== false) {
            $stats['chars']++;

            // 统计字母
            if (ctype_alpha($char)) {
                $stats['letters']++;
                if (!$stats['in_word']) {
                    $stats['words']++;
                    $stats['in_word'] = true;
                }
            }
            // 统计数字
            elseif (ctype_digit($char)) {
                $stats['digits']++;
                if (!$stats['in_word']) {
                    $stats['words']++;
                    $stats['in_word'] = true;
                }
            }
            // 统计空格
            elseif (ctype_space($char)) {
                $stats['spaces']++;
                $stats['in_word'] = false;

                // 统计行数
                if ($char === "\n") {
                    $stats['lines']++;
                }
            }
            // 统计标点符号
            elseif (ctype_punct($char)) {
                $stats['punctuation']++;
                $stats['in_word'] = false;
            } else {
                $stats['in_word'] = false;
            }

            $prev_char = $char;
        }

        rewind($this->handle);
        return $stats;
    }

    /**
     * 查找最长的单词
     */
    public function findLongestWord() {
        rewind($this->handle);

        $longest_word = '';
        $current_word = '';
        $in_word = false;

        while (($char = fgetc($this->handle)) !== false) {
            if (ctype_alnum($char)) {
                $current_word .= $char;
                $in_word = true;
            } else {
                if ($in_word) {
                    if (strlen($current_word) > strlen($longest_word)) {
                        $longest_word = $current_word;
                    }
                    $current_word = '';
                    $in_word = false;
                }
            }
        }

        // 检查最后一个单词
        if ($in_word && strlen($current_word) > strlen($longest_word)) {
            $longest_word = $current_word;
        }

        rewind($this->handle);
        return $longest_word;
    }

    /**
     * 搜索字符序列
     */
    public function searchSequence($sequence) {
        rewind($this->handle);

        $seq_length = strlen($sequence);
        $buffer = '';
        $positions = [];
        $position = 0;

        while (($char = fgetc($this->handle)) !== false) {
            $buffer .= $char;

            // 保持缓冲区长度与序列长度相同
            if (strlen($buffer) > $seq_length) {
                $buffer = substr($buffer, 1);
                $position++;
            }

            // 检查是否匹配
            if ($buffer === $sequence) {
                $positions[] = $position;
            }
        }

        rewind($this->handle);
        return $positions;
    }

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

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

// 使用示例
echo "文本分析工具演示:\n";
echo "==================\n";

// 创建测试文本文件
$text_file = "analysis_test.txt";
$text_content = "The quick brown fox jumps over the lazy dog.\n";
$text_content .= "This sentence contains 35 letters and 8 words.\n";
$text_content .= "Numbers: 123, 456, 789. Punctuation: !, ?, ,, ., ;, :\n";
$text_content .= "Special characters: @#$%^&*()_+-=[]{}|;:',./<>?`~\n";
$text_content .= "The longestwordinthistextis: supercalifragilisticexpialidocious\n";
file_put_contents($text_file, $text_content);

try {
    $analyzer = new TextAnalyzer($text_file);

    // 基本分析
    echo "基本文本分析:\n";
    $stats = $analyzer->analyzeBasic();

    echo "总字符数: " . $stats['chars'] . "\n";
    echo "字母数: " . $stats['letters'] . "\n";
    echo "数字数: " . $stats['digits'] . "\n";
    echo "空格数: " . $stats['spaces'] . "\n";
    echo "标点符号数: " . $stats['punctuation'] . "\n";
    echo "行数: " . $stats['lines'] . "\n";
    echo "单词数: " . $stats['words'] . "\n";

    // 查找最长单词
    echo "\n查找最长单词:\n";
    $longest = $analyzer->findLongestWord();
    echo "最长单词: \"" . $longest . "\" (长度: " . strlen($longest) . ")\n";

    // 搜索序列
    echo "\n搜索字符序列:\n";
    $search_seq = "the";
    $positions = $analyzer->searchSequence($search_seq);
    echo "序列 \"" . $search_seq . "\" 出现位置: ";
    if (empty($positions)) {
        echo "未找到";
    } else {
        echo implode(", ", $positions) . " (共 " . count($positions) . " 次)";
    }

    $analyzer->close();

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

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

示例 6:简单加密/解密工具

<?php
/**
 * 简单的字符级加密/解密工具
 * 使用fgetc()逐字符处理
 */
class SimpleCipher {
    private $key;

    public function __construct($key = 'secret') {
        $this->key = $key;
    }

    /**
     * 加密文件
     */
    public function encryptFile($source, $dest) {
        $source_handle = fopen($source, "r");
        $dest_handle = fopen($dest, "w");

        if (!$source_handle || !$dest_handle) {
            return false;
        }

        $key_length = strlen($this->key);
        $key_index = 0;

        while (($char = fgetc($source_handle)) !== false) {
            // 简单的XOR加密
            $encrypted_char = chr(ord($char) ^ ord($this->key[$key_index]));
            fwrite($dest_handle, $encrypted_char);

            $key_index = ($key_index + 1) % $key_length;
        }

        fclose($source_handle);
        fclose($dest_handle);

        return true;
    }

    /**
     * 解密文件(XOR加密是对称的)
     */
    public function decryptFile($source, $dest) {
        // XOR加密中,加密和解密是相同的操作
        return $this->encryptFile($source, $dest);
    }

    /**
     * Caesar密码加密
     */
    public function caesarEncryptFile($source, $dest, $shift = 3) {
        $source_handle = fopen($source, "r");
        $dest_handle = fopen($dest, "w");

        if (!$source_handle || !$dest_handle) {
            return false;
        }

        while (($char = fgetc($source_handle)) !== false) {
            // 只加密字母
            if (ctype_alpha($char)) {
                $base = ctype_upper($char) ? ord('A') : ord('a');
                $encrypted_char = chr((ord($char) - $base + $shift) % 26 + $base);
                fwrite($dest_handle, $encrypted_char);
            } else {
                fwrite($dest_handle, $char);
            }
        }

        fclose($source_handle);
        fclose($dest_handle);

        return true;
    }

    /**
     * Caesar密码解密
     */
    public function caesarDecryptFile($source, $dest, $shift = 3) {
        return $this->caesarEncryptFile($source, $dest, 26 - $shift);
    }
}

// 使用示例
echo "简单加密/解密工具演示:\n";
echo "======================\n";

// 创建测试文件
$plain_file = "plain.txt";
$encrypted_file = "encrypted.txt";
$decrypted_file = "decrypted.txt";

$plain_text = "Hello, this is a secret message!\n";
$plain_text .= "It contains sensitive information.\n";
$plain_text .= "123 numbers and special characters: !@#$%^&*()\n";
file_put_contents($plain_file, $plain_text);

echo "原始文本:\n";
echo $plain_text . "\n";

$cipher = new SimpleCipher("mysecretkey");

// XOR加密演示
echo "XOR加密演示:\n";
if ($cipher->encryptFile($plain_file, $encrypted_file)) {
    echo "✅ 加密成功\n";

    echo "加密后的内容(十六进制):\n";
    $encrypted_content = file_get_contents($encrypted_file);
    for ($i = 0; $i < min(50, strlen($encrypted_content)); $i++) {
        echo sprintf('%02X ', ord($encrypted_content[$i]));
    }
    echo "...\n\n";

    // 解密
    if ($cipher->decryptFile($encrypted_file, $decrypted_file)) {
        echo "✅ 解密成功\n";
        echo "解密后的内容:\n";
        echo file_get_contents($decrypted_file) . "\n";
    }
}

echo "\nCaesar密码演示(移位3):\n";
$caesar_encrypted = "caesar_encrypted.txt";
$caesar_decrypted = "caesar_decrypted.txt";

if ($cipher->caesarEncryptFile($plain_file, $caesar_encrypted, 3)) {
    echo "✅ Caesar加密成功\n";
    echo "加密后的内容:\n";
    echo file_get_contents($caesar_encrypted) . "\n";

    if ($cipher->caesarDecryptFile($caesar_encrypted, $caesar_decrypted, 3)) {
        echo "✅ Caesar解密成功\n";
        echo "解密后的内容:\n";
        echo file_get_contents($caesar_decrypted) . "\n";
    }
}

// 清理测试文件
$files = [$plain_file, $encrypted_file, $decrypted_file, $caesar_encrypted, $caesar_decrypted];
foreach ($files as $file) {
    if (file_exists($file)) {
        unlink($file);
    }
}
?>

fgetc() 与其他读取函数的对比

函数 读取单位 返回值 适用场景 性能
fgetc() 单个字符 字符串(1字符)或 false 字符级处理、文本分析、解析 较慢(逐字符I/O)
fgets() 一行 字符串(一行)或 false 行级处理、日志分析、配置读取 较快
fread() 指定字节数 字符串或 false 二进制文件、大文件分块读取 快(可控制缓冲区大小)
file_get_contents() 整个文件 字符串或 false 小文件读取、配置文件 最快(一次性读取)
fscanf() 格式化数据 混合类型 格式化文本解析 中等

相关函数

  • fgets() - 从文件指针中读取一行
  • fread() - 读取文件(可安全用于二进制文件)
  • fopen() - 打开文件或URL
  • fclose() - 关闭打开的文件
  • feof() - 测试文件指针是否到达文件末尾
  • ftell() - 返回文件指针读/写的位置
  • fseek() - 在文件指针中定位
  • rewind() - 倒回文件指针的位置
最佳实践
  1. 选择正确的读取函数:根据需求选择fgetc()、fgets()或fread()
  2. 处理多字节字符:对于UTF-8等编码,使用专门的函数或自定义逻辑
  3. 检查文件指针:使用fgetc()前确保文件指针有效
  4. 正确检测EOF:使用while (($char = fgetc($handle)) !== false)模式
  5. 考虑性能:fgetc()逐字符读取较慢,适用于小文件或需要字符级处理的场景
  6. 使用二进制模式:对于二进制文件或需要精确控制的情况,使用"rb"模式
  7. 错误处理:检查fgetc()的返回值,区分EOF和读取错误
  8. 资源管理:使用后关闭文件指针,避免资源泄漏
fgetc() 使用模式总结
模式1:基本逐字符读取
$handle = fopen($file, "r");
while (($char = fgetc($handle)) !== false) {
    // 处理每个字符
}
fclose($handle);
模式2:字符统计
$handle = fopen($file, "r");
$char_count = 0;
while (($char = fgetc($handle)) !== false) {
    if ($char === 'a') { // 统计特定字符
        $char_count++;
    }
}
fclose($handle);
模式3:简单解析器
$handle = fopen($file, "r");
$in_quotes = false;
$current_field = '';

while (($char = fgetc($handle)) !== false) {
    if ($char === '"') {
        $in_quotes = !$in_quotes;
    } elseif ($char === ',' && !$in_quotes) {
        // 字段结束
        processField($current_field);
        $current_field = '';
    } else {
        $current_field .= $char;
    }
}
fclose($handle);