在文件管理系統、磁盤配額監控、備份腳本等場景中,準確計算目錄佔用空間是基礎需求。本文提供一個健壯的遞歸計算函數,支持符號鏈接識別、權限錯誤容錯、大文件處理,並封裝了人性化的單位自動轉換功能。
<?php
/**
* 遞歸計算目錄大小(字節)
* @param string $directory 目錄絕對路徑
* @return int|false 總字節數,失敗返回false
*/
function dirSize($directory) {
// 路徑標準化
$directory = rtrim(realpath($directory), DIRECTORY_SEPARATOR);
if ($directory === false || !is_dir($directory)) {
trigger_error("目錄不存在或不可讀: $directory", E_USER_WARNING);
return false;
}
$size = 0;
$handle = @opendir($directory);
if (!$handle) {
trigger_error("無法打開目錄: $directory", E_USER_WARNING);
return false;
}
while (($file = readdir($handle)) !== false) {
// 跳過 . 和 ..
if ($file === '.' || $file === '..') continue;
$path = $directory . DIRECTORY_SEPARATOR . $file;
// 處理符號鏈接:避免循環引用導致無限遞歸
if (is_link($path)) {
// 可選:統計鏈接本身大小,或跳過,或解析真實目標
continue; // 此處選擇跳過鏈接
}
if (is_dir($path)) {
$subSize = dirSize($path);
if ($subSize !== false) $size += $subSize;
} elseif (is_file($path)) {
$fileSize = @filesize($path);
if ($fileSize !== false) $size += $fileSize;
// 注意:filesize對2GB+文件在32位系統可能失效,建議用stat()
}
}
closedir($handle);
return $size;
}
/**
* 字節轉換為人類可讀格式
* @param int $bytes 字節數
* @param int $precision 小數位數
* @return string 如 "1.5 MB"
*/
function formatBytes($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$value = $bytes / pow(1024, $pow);
return round($value, $precision) . ' ' . $units[$pow];
}
// 使用示例
$dir = __DIR__ . '/storage'; // 替換為實際目錄
$bytes = dirSize($dir);
if ($bytes !== false) {
echo "目錄大小: " . formatBytes($bytes); // 輸出如: 目錄大小: 12.34 MB
}
filesize() 返回負數,建議使用 fstat(fopen($path, 'r'))['size'] 或 exec("stat -c%s $path")RecursiveDirectoryIterator)<?php
function dirSizeIterator($directory) {
$directory = rtrim(realpath($directory), DIRECTORY_SEPARATOR);
if (!$directory || !is_dir($directory)) return false;
$size = 0;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory,
RecursiveDirectoryIterator::SKIP_DOTS |
RecursiveDirectoryIterator::FOLLOW_SYMLINKS
),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if ($file->isFile() && !$file->isLink()) {
$size += $file->getSize();
}
}
return $size;
}
SPL版本優勢:無遞歸深度限制、內存佔用恆定、自動處理權限錯誤(拋出異常可捕獲)、代碼更簡潔。
pcntl_fork 或多進程分割子目錄並行統計,最終匯總du -sb $dir 經C優化極快,可 exec() 調用(注意命令注入防護)