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

PHP 遞迴計算目錄大小並人性化輸出完整實作

     編輯:PHP 綜合知識

核心函式:getDirSize()

/**
 * 遞迴計算目錄總大小(位元組)
 * @param string $dir 目錄路徑
 * @return int 總位元組數
 * @throws InvalidArgumentException 路徑不存在或不可讀
 */
function getDirSize(string $dir): int {
    // 規範化路徑
    $dir = rtrim(realpath($dir), DIRECTORY_SEPARATOR);
    if ($dir === false || !is_dir($dir) || !is_readable($dir)) {
        throw new InvalidArgumentException("目錄不存在或不可讀: $dir");
    }
    
    $size = 0;
    $handle = opendir($dir);
    
    while (($entry = readdir($handle)) !== false) {
        if ($entry === '.' || $entry === '..') continue;
        
        $path = $dir . DIRECTORY_SEPARATOR . $entry;
        
        if (is_dir($path)) {
            // 遞迴處理子目錄
            $size += getDirSize($path);
        } elseif (is_file($path)) {
            // 累加檔案大小
            $size += filesize($path);
        }
        // 忽略連結、裝置檔等特殊類型
    }
    
    closedir($handle);
    return $size;
}

人性化格式化:formatBytes()

/**
 * 位元組轉人類可讀格式
 * @param int $bytes 位元組數
 * @param int $precision 小數位數
 * @return string 如 1.5 MB、2.3 GB
 */
function formatBytes(int $bytes, int $precision = 2): string {
    $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
    
    if ($bytes === 0) return '0 B';
    
    // 計算對應單位
    $exp = (int)floor(log($bytes, 1024));
    $exp = min($exp, count($units) - 1);  // 限制最大單位
    
    $value = $bytes / (1024 ** $exp);
    return round($value, $precision) . ' ' . $units[$exp];
}

進階版:支援排除、深度限制、快取

class DirectorySize {
    private array $excludePatterns = [];
    private int $maxDepth = -1;  // -1 無限制
    private array $cache = [];
    
    public function __construct(array $options = []) {
        $this->excludePatterns = $options['exclude'] ?? [];
        $this->maxDepth = $options['max_depth'] ?? -1;
    }
    
    public function calculate(string $dir, int $currentDepth = 0): int {
        $dir = rtrim(realpath($dir), DIRECTORY_SEPARATOR);
        
        // 快取鍵
        $cacheKey = $dir . '|' . $currentDepth;
        if (isset($this->cache[$cacheKey])) {
            return $this->cache[$cacheKey];
        }
        
        // 深度限制
        if ($this->maxDepth >= 0 && $currentDepth > $this->maxDepth) {
            return 0;
        }
        
        // 排除模式匹配
        foreach ($this->excludePatterns as $pattern) {
            if (fnmatch($pattern, basename($dir)) || fnmatch($pattern, $dir)) {
                return 0;
            }
        }
        
        $size = 0;
        $handle = @opendir($dir);
        if (!$handle) return 0;  // 權限不足靜默跳過
        
        while (($entry = readdir($handle)) !== false) {
            if ($entry === '.' || $entry === '..') continue;
            
            $path = $dir . DIRECTORY_SEPARATOR . $entry;
            
            if (is_dir($path)) {
                $size += $this->calculate($path, $currentDepth + 1);
            } elseif (is_file($path)) {
                $size += filesize($path);
            }
        }
        closedir($handle);
        
        return $this->cache[$cacheKey] = $size;
    }
    
    public function format(int $bytes): string {
        return formatBytes($bytes);
    }
}

// 使用
$calculator = new DirectorySize([
    'exclude' => ['.git', 'node_modules', 'vendor', '*.log', 'cache/*'],
    'max_depth' => 3,
]);

$bytes = $calculator->calculate('/var/www/project');
echo "專案大小: " . $calculator->format($bytes);  // 專案大小: 45.2 MB

效能優化建議

場景優化策略
超大目錄(10萬+檔案)使用 RecursiveDirectoryIterator + RecursiveIteratorIterator,C 層面遍歷更快
頻繁查詢同一目錄快取結果,監聽 inotify 事件失效
跨網路掛載(NFS/SMB)啟用非同步 I/O,避免阻塞
只需特定副檔名加入 pathinfo($path, PATHINFO_EXTENSION) 過濾

Iterator 版本(現代寫法)

function getDirSizeIterator(string $dir): int {
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    
    $size = 0;
    foreach ($iterator as $file) {
        if ($file->isFile()) {
            $size += $file->getSize();
        }
    }
    return $size;
}

實戰應用:磁碟配額監控

function checkDiskQuota(string $userDir, int $quotaMB): array {
    $bytes = getDirSize($userDir);
    $quotaBytes = $quotaMB * 1024 * 1024;
    $usagePercent = $quotaBytes > 0 ? ($bytes / $quotaBytes) * 100 : 0;
    
    return [
        'used' => formatBytes($bytes),
        'quota' => formatBytes($quotaBytes),
        'percent' => round($usagePercent, 1),
        'exceeded' => $bytes > $quotaBytes,
        'remaining' => formatBytes(max(0, $quotaBytes - $bytes)),
    ];
}

// 使用
$quota = checkDiskQuota('/home/user1', 1024);  // 1GB 配額
if ($quota['exceeded']) {
    // 發送告警、限制寫入...
}

避坑指南

  1. 權限錯誤:@opendir 抑制警告,不可讀目錄返回 0 而非拋出異常
  2. 符號連結循環:is_dir() 對 symlink 返回 true,需用 is_link() 排除或追蹤已訪問 inode
  3. 大檔案溢位:32 位系統 filesize() 最大 2GB,需用 stat()['size'] 或 sprintf('%u', filesize())
  4. 執行逾時:大目錄遞迴耗時長,CLI 執行設定 set_time_limit(0)

總結

getDirSize() + formatBytes() 是經典組合。現代專案建議封裝為 DirectorySize 類別,支援排除模式、深度限制、快取機制。生產環境注意權限處理、符號連結、大檔案與執行時間限制。

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