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

PHP 檔案與目錄操作大全:從屬性讀取到遞歸處理的完整實戰

     編輯:PHP 相關知識

前言:為什麼要精通檔案系統操作?

變數生命週期隨請求結束而銷毀,持久化資料必須落盤。無論是日誌記錄、快取檔案、上傳處理、匯出報表、部署腳本,檔案與目錄操作都是 PHP 後端開發的核心基本功。本文系統整理屬性讀取、目錄遍歷、遞歸計算/刪除/複製、檔案讀寫指標控制等實戰技巧。

一、檔案屬性與元資訊獲取

1. 類型判斷

function getFileType(string $path): string
{
    return match (filetype($path)) {
        'file' => '普通檔案',
        'dir'  => '目錄',
        'link' => '符號連結',
        'block', 'char', 'fifo' => '特殊裝置檔',
        default => '未知類型',
    };
}
Windows 僅支援 file、dir、unknown 三種;Linux 可見完整 7 類型。

2. 常用屬性函數速查

函數用途回傳值
file_exists()檔案/目錄是否存在bool
is_file() / is_dir() / is_link()類型判斷bool
filesize()檔案大小(位元組)int|false
is_readable() / is_writable() / is_executable()權限檢查bool
filectime() / filemtime() / fileatime()建立/修改/存取時間Unix timestamp
fileowner() / filegroup()擁有者 UID / 群組 GIDint
fileperms()權限位元遮罩int(需配合 sprintf('%o', ...) 看八進制)
stat()一次性取得所有屬性混合索引/關聯陣列

3. 人類可讀的檔案大小格式化

function formatSize(int $bytes): string
{
    $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
    $bytes = max($bytes, 0);
    $pow = $bytes ? floor(log($bytes, 1024)) : 0;
    $pow = min($pow, count($units) - 1);
    $value = $bytes / (1 << (10 * $pow));
    return round($value, 2) . ' ' . $units[$pow];
}

4. 綜合資訊輸出範例

function printFileInfo(string $path): void
{
    if (!file_exists($path)) {
        echo "路徑不存在: $path\n";
        return;
    }

    $info = [
        '類型' => getFileType($path),
        '大小' => formatSize(filesize($path)),
        '權限' => substr(sprintf('%o', fileperms($path)), -4),
        '擁有者' => fileowner($path),
        '群組' => filegroup($path),
        '建立時間' => date('Y-m-d H:i:s', filectime($path)),
        '修改時間' => date('Y-m-d H:i:s', filemtime($path)),
        '最後存取' => date('Y-m-d H:i:s', fileatime($path)),
        '可讀' => is_readable($path) ? '是' : '否',
        '可寫' => is_writable($path) ? '是' : '否',
        '可執行' => is_executable($path) ? '是' : '否',
    ];

    foreach ($info as $k => $v) echo "$k: $v\n";
}

二、目錄操作:遍歷、計算大小、建立、刪除

1. 讀取目錄內容(非遞歸)

function listDirectory(string $dir): array
{
    if (!is_dir($dir)) return [];

    $files = [];
    $handle = opendir($dir);
    while (false !== ($name = readdir($handle))) {
        if ($name === '.' || $name === '..') continue; // 關鍵:過濾當前/上層目錄
        $full = $dir . DIRECTORY_SEPARATOR . $name;
        $files[] = [
            'name' => $name,
            'path' => $full,
            'type' => filetype($full),
            'size' => is_file($full) ? filesize($full) : null,
            'mtime' => filemtime($full),
        ];
    }
    closedir($handle);
    return $files;
}

2. 遞歸計算目錄總大小

function getDirSize(string $dir): int
{
    $size = 0;
    $handle = opendir($dir);
    while (false !== ($entry = readdir($handle))) {
        if ($entry === '.' || $entry === '..') continue;
        $full = $dir . DIRECTORY_SEPARATOR . $entry;
        if (is_file($full)) {
            $size += filesize($full);
        } elseif (is_dir($full)) {
            $size += getDirSize($full); // 遞歸
        }
    }
    closedir($handle);
    return $size;
}

// 使用
echo "目錄大小: " . formatSize(getDirSize('/var/www/html'));

3. 建立多層目錄

// recursive=true 允許建立巢狀目錄,mode=0775 受 umask 影響
mkdir('/var/www/storage/logs/2024/05', 0775, true);

4. 遞歸刪除非空目錄(含檔案與子目錄)

function removeDirRecursive(string $dir): bool
{
    if (!is_dir($dir)) return false;

    $handle = opendir($dir);
    while (false !== ($entry = readdir($handle))) {
        if ($entry === '.' || $entry === '..') continue;
        $full = $dir . DIRECTORY_SEPARATOR . $entry;
        is_dir($full) ? removeDirRecursive($full) : unlink($full);
    }
    closedir($handle);
    return rmdir($dir);
}
核心陷阱: 必須過濾 . 和 ..,否則遞歸會陷入無限循環(. 指向自身)。

5. 遞歸複製目錄

function copyDirRecursive(string $src, string $dst): bool
{
    if (!is_dir($src)) return false;
    if (!is_dir($dst)) mkdir($dst, 0775, true);

    $handle = opendir($src);
    while (false !== ($entry = readdir($handle))) {
        if ($entry === '.' || $entry === '..') continue;
        $srcPath = $src . DIRECTORY_SEPARATOR . $entry;
        $dstPath = $dst . DIRECTORY_SEPARATOR . $entry;
        if (is_dir($srcPath)) {
            copyDirRecursive($srcPath, $dstPath);
        } else {
            copy($srcPath, $dstPath);
        }
    }
    closedir($handle);
    return true;
}

6. 遞歸移動/重命名(利用 rename 跨目錄即為移動)

// 移動整個目錄
rename('/tmp/old_project', '/var/www/new_project'); // 原子操作,極快

三、檔案讀寫:模式、指標控制、高效讀取

1. 開啟模式速查

模式說明指標位置建檔截斷
r唯讀開頭否否
r+讀寫開頭否否
w唯寫開頭是是
w+讀寫開頭是是
a追加寫入結尾是否
a+讀寫追加結尾是否
x獨佔建立寫入開頭僅不存在時否
c不截斷開啟寫入開頭是否

2. 寫入檔案

// 單次寫入(自動開啟/關閉,支援 FLAG)
file_put_contents('log.txt', "新日誌\n", FILE_APPEND | LOCK_EX);

// 手動控制(大量寫入、需精確指標)
$fp = fopen('data.csv', 'w');
foreach ($rows as $row) {
    fputcsv($fp, $row); // 自動處理 CSV 轉義
}
fclose($fp);

3. 讀取策略對比

函數適用場景記憶體指標行為
fread($fp, $len)二進位、固定長度可控前進 $len 位元組
fgets($fp)逐行讀取文字單行前進至換行符
fgetc($fp)逐字元單字元前進 1 字元
file($path)小檔案全讀成陣列O(n)不適用
file_get_contents()小檔案全讀成字串O(n)不適用
readfile()直接輸出到瀏覽器流式不適用

4. 指標精細控制(fseek / rewind / ftell)

$fp = fopen('alphabet.txt', 'r'); // 內容: abcdefghijklmnopqrstuvwxyz

echo "起始位置: " . ftell($fp) . "\n"; // 0
fread($fp, 3); // 讀 'abc'
echo "讀 3 字元後: " . ftell($fp) . "\n"; // 3

fseek($fp, 5, SEEK_CUR); // 從目前位置向後 5 字元
echo "SEEK_CUR +5: " . ftell($fp) . "\n"; // 8

rewind($fp); // 回到開頭
echo "rewind: " . ftell($fp) . "\n"; // 0

fseek($fp, 0, SEEK_END); // 跳到結尾
echo "SEEK_END: " . ftell($fp) . "\n"; // 26 (檔案大小)
fclose($fp);
常數: SEEK_SET(預設,從開頭)、SEEK_CUR(從目前)、SEEK_END(從結尾)。

5. 判斷檔案結尾

while (!feof($fp)) {
    $line = fgets($fp);
    if ($line === false) break; // 實際讀取失敗
    // 處理 $line
}

四、實戰最佳實踐清單

  1. 路徑統一使用 DIRECTORY_SEPARATOR 或 /(PHP 自動轉換),避免跨平台問題。
  2. 檔案操作前驗證: is_readable()/is_writable(),失敗拋出具體異常。
  3. 嚴格關閉資源: fclose()、closedir(),或使用 try/finally 確保釋放。
  4. 大檔案採用流式處理: 逐行/分塊讀寫,避免 file_get_contents() 撐爆記憶體。
  5. 併發寫入加鎖: flock($fp, LOCK_EX) 獨佔鎖,LOCK_SH 共享鎖。
  6. 權限最小化原則: 目錄 0755、檔案 0644,敏感設定檔 0600。
  7. 臨時檔案用 sys_get_temp_dir() + tempnam(),用畢 unlink() 清理。

結語

檔案系統操作看似基礎,實則藏著跨平台相容性、效能陷阱、併發安全等深層課題。掌握 SplFileInfo、RecursiveDirectoryIterator 等 SPL 類別,能進一步簡化代碼、提升健壯性。建議將常用工具函數封裝為 Filesystem 服務類,統一錯誤處理與日誌記錄,讓業務代碼專注於核心邏輯。

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