在 PHP 開發中,遍歷目錄下所有文件(包含子目錄)是常見需求。最直觀的思路是使用遞歸:先處理當前目錄,再依次處理子目錄。但遞歸並非唯一解法,理解其底層堆棧機制後,我們可以將遞歸轉換為非遞歸實現,避免深層遞歸導致的棧溢出風險。
非遞歸遍歷本質上是手動模擬堆棧操作,主要分三步:
. 和 ..),將完整路徑壓入堆棧。/**
* 非遞歸遍歷目錄下所有文件
* @param string $dir 起始目錄路徑
*/
function scanAll($dir) {
$stack = [$dir]; // 初始化堆棧
while (count($stack) > 0) {
// 彈出棧頂元素(後進先出,深度優先)
$file = array_pop($stack);
// 處理當前文件/目錄(此處僅輸出路徑)
echo $file . PHP_EOL;
// 若為目錄,掃描子元素並壓入堆棧
if (is_dir($file)) {
$children = scandir($file);
foreach ($children as $child) {
if ($child !== '.' && $child !== '..') {
$stack[] = $file . '/' . $child;
}
}
}
}
}
遞歸版本代碼更簡潔:
function scanAllRecursive($dir) {
echo $dir . PHP_EOL;
if (is_dir($dir)) {
$children = scandir($dir);
foreach ($children as $child) {
if ($child !== '.' && $child !== '..') {
scanAllRecursive($dir . '/' . $child);
}
}
}
}
關鍵差異在於輸出順序:非遞歸版本因堆棧後進先出特性,配合 scandir() 返回的自然順序,會導致遍歷順序「倒序」。若需保持與遞歸一致的順序,可在壓棧前反轉數組:
$children = array_reverse(scandir($file));
實際項目中,若目錄層級不深(如常見 CMS 附件目錄),遞歸足矣;若需處理用戶上傳的任意深度目錄,或長時間運行的後台任務,非遞歸更穩健。