readdir() 函数用于从由 opendir() 打开的目录句柄中读取条目(文件或目录名)。
每次调用 readdir() 都会返回目录中的下一个条目,直到没有更多条目时返回 false。通常与 opendir() 和 closedir() 函数一起使用,遍历目录内容。
readdir(?resource $dir_handle = null): string|false
| 参数 | 描述 |
|---|---|
| dir_handle |
可选。由 如果省略此参数或设置为 从 PHP 8.0.0 开始,此参数可以为 null。 |
| 返回值 | 描述 |
|---|---|
| string | 成功时返回目录中的一个条目名称(文件名或目录名) |
| FALSE | 当没有更多条目可读取时返回 false |
readdir() 在遍历时会返回两个特殊的目录条目:.(当前目录)和 ..(父目录)。在大多数情况下,需要过滤掉这两个条目。
正确的使用方法是:while (false !== ($entry = readdir($handle))),而不是 while ($entry = readdir($handle)),因为如果目录中有名为 "0" 的文件,后者会提前终止循环。
<?php
// 打开目录
$dir = "/tmp";
$handle = opendir($dir);
if ($handle) {
echo "目录内容 ($dir):<br>";
// 正确的方式:使用 !== false 比较
while (false !== ($entry = readdir($handle))) {
echo htmlspecialchars($entry) . "<br>";
}
// 关闭目录句柄
closedir($handle);
} else {
echo "无法打开目录: $dir";
}
?>
<?php
/**
* 获取目录中的文件和子目录(排除 . 和 ..)
* @param string $directory 目录路径
* @return array 排序后的目录内容
*/
function getDirectoryContents($directory) {
$contents = [];
if ($handle = opendir($directory)) {
while (false !== ($entry = readdir($handle))) {
// 过滤 . 和 ..
if ($entry !== '.' && $entry !== '..') {
$contents[] = $entry;
}
}
closedir($handle);
// 按字母顺序排序
sort($contents);
}
return $contents;
}
// 使用示例
$directory = "/tmp";
$contents = getDirectoryContents($directory);
echo "目录内容 ($directory) - 已排序:<br>";
if (count($contents) > 0) {
foreach ($contents as $item) {
echo "- " . htmlspecialchars($item) . "<br>";
}
} else {
echo "目录为空";
}
?>
<?php
/**
* 分别列出目录中的文件和子目录
* @param string $path 目录路径
*/
function listFilesAndDirs($path) {
if (!is_dir($path)) {
echo "$path 不是有效的目录";
return;
}
$handle = opendir($path);
if (!$handle) {
echo "无法打开目录: $path";
return;
}
$files = [];
$directories = [];
while (false !== ($entry = readdir($handle))) {
if ($entry === '.' || $entry === '..') {
continue;
}
$fullPath = $path . '/' . $entry;
if (is_dir($fullPath)) {
$directories[] = $entry;
} elseif (is_file($fullPath)) {
$files[] = $entry;
}
}
closedir($handle);
// 排序
sort($files);
sort($directories);
// 输出结果
echo "<h4>目录: " . htmlspecialchars($path) . "</h4>";
echo "<h5>子目录 (" . count($directories) . "):</h5>";
if (count($directories) > 0) {
echo "<ul>";
foreach ($directories as $dir) {
echo "<li>" . htmlspecialchars($dir) . "/</li>";
}
echo "</ul>";
} else {
echo "<p>没有子目录</p>";
}
echo "<h5>文件 (" . count($files) . "):</h5>";
if (count($files) > 0) {
echo "<ul>";
foreach ($files as $file) {
echo "<li>" . htmlspecialchars($file) . "</li>";
}
echo "</ul>";
} else {
echo "<p>没有文件</p>";
}
}
// 使用示例
listFilesAndDirs("/tmp");
?>
<?php
/**
* 在目录中查找特定扩展名的文件
* @param string $directory 要搜索的目录
* @param array|string $extensions 扩展名或扩展名数组
* @return array 匹配的文件列表
*/
function findFilesByExtension($directory, $extensions) {
$foundFiles = [];
// 如果传递的是字符串,转换为数组
if (is_string($extensions)) {
$extensions = [$extensions];
}
// 转换为小写以便不区分大小写比较
$extensions = array_map('strtolower', $extensions);
if ($handle = opendir($directory)) {
while (false !== ($entry = readdir($handle))) {
if ($entry === '.' || $entry === '..') {
continue;
}
$fullPath = $directory . '/' . $entry;
// 只处理文件
if (is_file($fullPath)) {
$fileExtension = strtolower(pathinfo($entry, PATHINFO_EXTENSION));
if (in_array($fileExtension, $extensions)) {
$foundFiles[] = [
'name' => $entry,
'path' => $fullPath,
'size' => filesize($fullPath),
'modified' => filemtime($fullPath)
];
}
}
}
closedir($handle);
}
return $foundFiles;
}
// 使用示例
$directory = "/tmp";
$phpFiles = findFilesByExtension($directory, ['php', 'php5', 'php7', 'phtml']);
echo "在 $directory 中找到的PHP文件:<br>";
if (count($phpFiles) > 0) {
foreach ($phpFiles as $file) {
$size = round($file['size'] / 1024, 2); // 转换为KB
$date = date('Y-m-d H:i:s', $file['modified']);
echo "- {$file['name']} ({$size}KB, 修改于: $date)<br>";
}
} else {
echo "没有找到PHP文件";
}
?>
<?php
// 打开目录
$handle = opendir("/tmp");
if ($handle) {
echo "第一次读取(前5个条目):<br>";
$count = 0;
while (false !== ($entry = readdir($handle)) && $count < 5) {
echo $entry . "<br>";
$count++;
}
echo "<br>目录指针位置: " . (int)$handle . "<br><br>";
// 重置目录指针到开头
rewinddir($handle);
echo "使用 rewinddir() 重置指针后<br><br>";
echo "第二次读取(所有条目):<br>";
$total = 0;
while (false !== ($entry = readdir($handle))) {
// 跳过 . 和 ..
if ($entry !== '.' && $entry !== '..') {
echo $entry . "<br>";
$total++;
}
}
echo "<br>总计文件/目录数(排除 . 和 ..): $total<br>";
closedir($handle);
}
?>
<?php
/**
* 分批处理大型目录,避免内存溢出
* @param string $directory 要处理的目录
* @param callable $processor 处理每个条目的回调函数
* @param int $batchSize 每批处理的条目数
* @return int 处理的条目总数
*/
function processLargeDirectory($directory, $processor, $batchSize = 100) {
$handle = opendir($directory);
if (!$handle) {
return 0;
}
$processed = 0;
$batchCount = 0;
while (false !== ($entry = readdir($handle))) {
if ($entry === '.' || $entry === '..') {
continue;
}
// 处理条目
$processor($entry, $directory);
$processed++;
$batchCount++;
// 每处理 $batchSize 个条目,可以执行一些清理操作
if ($batchCount >= $batchSize) {
// 例如:输出进度、释放内存等
echo "已处理 $processed 个条目...<br>";
if (function_exists('gc_collect_cycles')) {
gc_collect_cycles(); // 垃圾回收
}
$batchCount = 0;
}
}
closedir($handle);
echo "处理完成,总计 $processed 个条目<br>";
return $processed;
}
// 使用示例:统计目录中各种文件类型的数量
$directory = "/tmp";
$fileTypes = [];
// 定义处理函数
$processor = function($entry, $dir) use (&$fileTypes) {
$fullPath = $dir . '/' . $entry;
if (is_file($fullPath)) {
$extension = pathinfo($entry, PATHINFO_EXTENSION);
if (empty($extension)) {
$extension = '无扩展名';
}
if (!isset($fileTypes[$extension])) {
$fileTypes[$extension] = 0;
}
$fileTypes[$extension]++;
}
};
echo "开始处理目录: $directory<br>";
processLargeDirectory($directory, $processor, 50);
echo "<br>文件类型统计:<br>";
if (count($fileTypes) > 0) {
arsort($fileTypes); // 按数量降序排序
foreach ($fileTypes as $type => $count) {
echo "$type: $count 个文件<br>";
}
}
?>
| 错误写法 | 问题 | 正确写法 |
|---|---|---|
while ($file = readdir($handle)) |
如果目录中有名为 "0"、"false" 或空字符串的文件,循环会提前终止 | while (false !== ($file = readdir($handle))) |
while (($file = readdir($handle)) != false) |
!= 操作符会进行类型转换,"0" 会被转换为 false | while (false !== ($file = readdir($handle))) |
| 忘记过滤 . 和 .. | 会包含当前目录和父目录的引用,通常这不是期望的结果 | 在循环内添加条件:if ($file == '.' || $file == '..') continue; |
| 忘记关闭目录句柄 | 可能导致资源泄漏,特别是在长时间运行的脚本中 | 始终在最后调用 closedir($handle) |
| 特性 | readdir() | scandir() |
|---|---|---|
| 内存使用 | 低(一次读取一个条目) | 高(一次性加载所有条目到数组) |
| 性能 | 适合大型目录,可提前中断 | 适合小型目录,简单直接 |
| 控制能力 | 高(可以控制读取过程) | 低(必须处理整个数组) |
| 资源管理 | 需要手动打开和关闭句柄 | 自动管理,不需要显式关闭 |
| 排序 | 需要手动排序结果 | 可指定排序方式 |
| 推荐场景 | 大型目录、需要流式处理、提前中断 | 小型目录、需要简单列表、需要排序 |
!== false 而不是 != false 或简单的赋值. 和 .. 条目,除非确实需要它们