<?php
/**
* 計算目錄總大小(位元組)
*
* @param string $dir 絕對路徑
* @return int|false 位元組數,失敗返回 false
*/
function dirSize(string $dir): int|false {
$dir = rtrim($dir, '/\\');
if (!is_dir($dir) || !is_readable($dir)) {
return false;
}
$size = 0;
$handle = @opendir($dir);
if (!$handle) return false;
while (($entry = readdir($handle)) !== false) {
if ($entry === '.' || $entry === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $entry;
if (is_link($path)) {
// 符號連結:可選擇跳過、計算連結本身大小、或遞歸目標
// 這裡跳過,避免循環引用
continue;
} elseif (is_dir($path)) {
$subSize = dirSize($path);
if ($subSize === false) continue; // 無權限讀取子目錄,跳過
$size += $subSize;
} elseif (is_file($path)) {
$size += filesize($path);
}
}
closedir($handle);
return $size;
}
<?php
function formatBytes(int $bytes, int $precision = 2): string {
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$value = $bytes / (1024 ** $pow);
return round($value, $precision) . ' ' . $units[$pow];
}
// 使用
$path = '/var/www/html';
$bytes = dirSize($path);
if ($bytes !== false) {
echo "目錄大小: " . formatBytes($bytes); // 例:123.45 MB
} else {
echo "無法讀取目錄: $path";
}
<?php
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
function dirSizeIterator(string $dir): int|false {
if (!is_dir($dir) || !is_readable($dir)) return false;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
$size = 0;
foreach ($iterator as $file) {
if ($file->isFile() && !$file->isLink()) {
$size += $file->getSize();
}
}
return $size;
}
./..、支援 SKIP_DOTS、可過濾 isLink()| 場景 | 處理策略 |
|---|---|
| 目錄不可讀 | 返回 false,上層決定是否拋出異常或記錄日誌 |
| 子目錄不可讀 | 跳過該子樹,累加可讀部分,可選記錄警告 |
| 符號連結循環 | RecursiveDirectoryIterator::FOLLOW_SYMLINKS **不可設**,或手動維護 SplObjectStorage 追蹤已訪問 inode |
| 超大目錄(百萬檔) | 分批處理、生成器 yield 避免記憶體峰值、或改用 du -sb 系統命令 |
<?php
function dirSizeDu(string $dir): int|false {
$dir = escapeshellarg($dir);
$output = [];
$returnVar = 0;
// -s: summarize, -b: bytes, --exclude 排除模式
exec("du -sb $dir 2>/dev/null", $output, $returnVar);
if ($returnVar !== 0 || empty($output)) return false;
// 輸出格式: "12345678\t/path/to/dir"
return (int)explode("\t", $output[0])[0];
}
優點: 核心級實現、極快、自動處理硬連結/稀疏檔/掛載點。
缺點: 依賴 du 命令(Linux/Unix)、需 exec 權限、Windows 不支援。
<?php
class DirectorySizeCache {
private array $cache = [];
private int $ttl = 300; // 5 分鐘
public function getSize(string $dir): int|false {
$key = realpath($dir) ?: $dir;
if (isset($this->cache[$key]) && (time() - $this->cache[$key]['time'] < $this->ttl)) {
return $this->cache[$key]['size'];
}
$size = dirSizeIterator($key);
if ($size !== false) {
$this->cache[$key] = ['size' => $size, 'time' => time()];
}
return $size;
}
}
PHP 計算目錄大小的標準解是 RecursiveDirectoryIterator + RecursiveIteratorIterator:簡潔、健壯、效能佳。搭配單位格式化、權限容錯、快取機制,即可滿足從後台管理面板到自動化部署腳本的各種需求。極大目錄(>100 萬檔)建議改用 du -sb 系統命令或離線預計算任務。