首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 文件編碼檢測與自動轉碼讀取實戰方案

     編輯:PHP 相關知識

mb_detect_encoding 為何不可靠?

mb_detect_encoding() 基於字節特徵猜測,對短文本、純 ASCII、GBK/UTF-8 混雜極易誤判。原文利用 「編碼轉換往返一致性」 原理構建檢測器,是工程上可行的啟發式方案。

核心原理

若字符串 $str 的真實編碼為 $enc,則:

mb_convert_encoding($str, $enc, $enc) === $str  // 恒成立
mb_convert_encoding($str, 'UTF-8', $enc)         // 可正確轉為 UTF-8

遍歷候選編碼列表,找到滿足往返一致性的編碼即為真實編碼。

生產級實現:編碼檢測器類

<?php
/**
 * 文件編碼檢測與安全讀取
 * 支持:UTF-8, GBK/GB2312/GB18030, UTF-16LE/BE, ISO-8859-1, Big5 等
 */
class EncodingDetector
{
    /** @var array 候選編碼優先級(常用在前) */
    private array $candidates = [
        'UTF-8', 'GBK', 'GB2312', 'GB18030', 
        'UTF-16LE', 'UTF-16BE', 'UTF-32LE', 'UTF-32BE',
        'Big5', 'ISO-8859-1', 'ASCII'
    ];
    
    /** @var int 讀取樣本字節數(大文件無需全讀) */
    private int $sampleSize = 8192;
    
    public function __construct(array $customCandidates = [], int $sampleSize = 8192)
    {
        if ($customCandidates) $this->candidates = $customCandidates;
        $this->sampleSize = $sampleSize;
    }
    
    /**
     * 檢測文件編碼
     * @return string|null 編碼名稱或 null(無法確定)
     */
    public function detectFile(string $filePath): ?string
    {
        if (!is_readable($filePath)) return null;
        
        $fp = fopen($filePath, 'rb');
        $sample = fread($fp, $this->sampleSize);
        fclose($fp);
        
        if ($sample === '') return 'ASCII'; // 空文件
        
        // BOM 優先檢測(最可靠)
        $bom = $this->detectBOM($sample);
        if ($bom) return $bom;
        
        return $this->detectByConsistency($sample);
    }
    
    /**
     * 自動讀取並轉為目標編碼(默認 UTF-8)
     */
    public function readAs(string $filePath, string $targetEncoding = 'UTF-8'): string
    {
        $encoding = $this->detectFile($filePath);
        if (!$encoding) {
            // 兜底:嘗試作為 UTF-8 讀取,替換無效字節
            $content = file_get_contents($filePath);
            return mb_convert_encoding($content, $targetEncoding, 'UTF-8');
        }
        
        $content = file_get_contents($filePath);
        return mb_convert_encoding($content, $targetEncoding, $encoding);
    }
    
    /** BOM 檢測 */
    private function detectBOM(string $sample): ?string
    {
        $boms = [
            "\xEF\xBB\xBF" => 'UTF-8',
            "\xFF\xFE\x00\x00" => 'UTF-32LE',
            "\x00\x00\xFE\xFF" => 'UTF-32BE',
            "\xFF\xFE" => 'UTF-16LE',
            "\xFE\xFF" => 'UTF-16BE',
        ];
        foreach ($boms as $bytes => $enc) {
            if (str_starts_with($sample, $bytes)) return $enc;
        }
        return null;
    }
    
    /** 一致性檢測核心算法 */
    private function detectByConsistency(string $sample): ?string
    {
        // 先嘗試 UTF-8(最常見)
        if ($this->isValidUtf8($sample)) return 'UTF-8';
        
        foreach ($this->candidates as $enc) {
            if ($enc === 'UTF-8') continue; // 已檢測
            if ($this->checkRoundtrip($sample, $enc)) {
                return $enc;
            }
        }
        return null;
    }
    
    /** 嚴格 UTF-8 驗證 */
    private function isValidUtf8(string $str): bool
    {
        // 使用 mb_check_encoding (PHP 7.1+) 或正則
        return mb_check_encoding($str, 'UTF-8');
    }
    
    /** 往返一致性檢查 */
    private function checkRoundtrip(string $str, string $enc): bool
    {
        try {
            $converted = mb_convert_encoding($str, $enc, $enc);
            // 使用二進制安全比較
            return $converted === $str;
        } catch (ValueError $e) {
            return false; // 不支持的編碼或非法字節序列
        }
    }
}

// ===== 使用示例 =====
$detector = new EncodingDetector();

// 1. 檢測單文件
$file = 'data.csv';
$enc = $detector->detectFile($file);
echo "檢測編碼: " . ($enc ?? '未知') . "\n";

// 2. 直接讀取為 UTF-8
$content = $detector->readAs($file, 'UTF-8');
echo "前 200 字符: " . mb_substr($content, 0, 200) . "\n";

// 3. 批量處理目錄
$dir = new RecursiveDirectoryIterator('/path/to/files');
foreach (new RecursiveIteratorIterator($dir) as $file) {
    if ($file->isFile() && $file->getExtension() === 'csv') {
        $content = $detector->readAs($file->getPathname());
        // 導入數據庫等後續處理...
    }
}

性能優化建議

  • 大文件抽樣:默認 8KB 樣本足夠,避免全文件讀入內存
  • 緩存結果:同一文件多次處理時緩存編碼結果
  • 並行處理:批量導入場景使用 parallel 擴展或進程池
  • 排除二進制:預先用 finfo_file 排除圖片/視頻等非文本文件

擴展:處理混合編碼文件

極少數文件不同行/段使用不同編碼(如日誌文件),需逐行檢測:

function readMixedEncoding(string $filePath): Generator
{
    $fp = fopen($filePath, 'rb');
    $detector = new EncodingDetector();
    while (($line = fgets($fp)) !== false) {
        $enc = $detector->detectByConsistency($line) ?? 'UTF-8';
        yield mb_convert_encoding($line, 'UTF-8', $enc);
    }
    fclose($fp);
}

掌握「BOM 優先 → UTF-8 嚴格驗證 → 候選編碼往返驗證」三級檢測策略,能解決 99% 的中文編碼亂碼問題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved