scandir() 函数返回指定目录中的文件和目录的数组。该函数提供了一种简单的方式来获取目录内容,无需手动打开和读取目录句柄。
与 opendir()/readdir()/closedir() 相比,scandir() 更简单易用,因为它一次性返回所有结果,并且不需要显式的资源管理。
.(当前目录)和 ..(父目录)。通常需要过滤掉这些条目。
scandir(string $directory, int $sorting_order = SCANDIR_SORT_ASCENDING, ?resource $context = null): array|false
| 参数 | 描述 |
|---|---|
| directory |
必需。要扫描的目录路径。 可以是绝对路径或相对路径。相对路径将相对于当前工作目录进行解析。 |
| sorting_order |
可选。指定排序方式:
使用 |
| context |
可选。流上下文(stream context)资源。 用于指定流的特殊选项,例如访问 FTP、HTTP 或其他流包装器时需要。 |
| 返回值 | 描述 |
|---|---|
| array | 成功时返回包含目录中所有文件和目录的数组 |
| FALSE | 扫描失败时返回 false(目录不存在、没有权限或路径无效) |
<?php
// 获取目录内容
$directory = "/tmp";
$files = scandir($directory);
if ($files !== false) {
echo "目录 $directory 中的内容:<br>";
foreach ($files as $file) {
echo htmlspecialchars($file) . "<br>";
}
echo "<br>总计: " . count($files) . " 个条目";
} else {
echo "无法扫描目录: $directory";
}
?>
<?php
$directory = "/tmp";
// 默认升序排序
$ascending = scandir($directory, SCANDIR_SORT_ASCENDING);
echo "升序排序:<br>";
print_r($ascending);
echo "<br><br>";
// 降序排序
$descending = scandir($directory, SCANDIR_SORT_DESCENDING);
echo "降序排序:<br>";
print_r($descending);
echo "<br><br>";
// 不排序(系统顺序)
$none = scandir($directory, SCANDIR_SORT_NONE);
echo "不排序(系统顺序):<br>";
print_r($none);
// 性能对比
echo "<br><br>性能统计:<br>";
$start = microtime(true);
scandir($directory, SCANDIR_SORT_ASCENDING);
$time_asc = microtime(true) - $start;
$start = microtime(true);
scandir($directory, SCANDIR_SORT_NONE);
$time_none = microtime(true) - $start;
echo "升序排序耗时: " . round($time_asc * 1000, 3) . " 毫秒<br>";
echo "不排序耗时: " . round($time_none * 1000, 3) . " 毫秒<br>";
echo "性能提升: " . round((1 - $time_none/$time_asc) * 100, 2) . "%";
?>
<?php
/**
* 获取目录中所有图片文件
* @param string $directory 目录路径
* @return array 图片文件数组
*/
function getImageFiles($directory) {
$allFiles = scandir($directory);
if ($allFiles === false) {
return [];
}
$imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
$imageFiles = [];
foreach ($allFiles as $file) {
// 跳过 . 和 ..
if ($file === '.' || $file === '..') {
continue;
}
// 检查是否是文件
$fullPath = $directory . '/' . $file;
if (!is_file($fullPath)) {
continue;
}
// 检查扩展名
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (in_array($extension, $imageExtensions)) {
$imageFiles[] = $file;
}
}
return $imageFiles;
}
/**
* 获取目录中所有子目录
* @param string $directory 目录路径
* @return array 子目录数组
*/
function getSubdirectories($directory) {
$allItems = scandir($directory);
if ($allItems === false) {
return [];
}
$subdirectories = [];
foreach ($allItems as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$fullPath = $directory . '/' . $item;
if (is_dir($fullPath)) {
$subdirectories[] = $item;
}
}
return $subdirectories;
}
// 使用示例
$directory = "/var/www/html";
echo "目录: $directory<br><br>";
$images = getImageFiles($directory);
echo "图片文件 (" . count($images) . "):<br>";
foreach ($images as $image) {
echo "- $image<br>";
}
echo "<br>";
$subdirs = getSubdirectories($directory);
echo "子目录 (" . count($subdirs) . "):<br>";
foreach ($subdirs as $dir) {
echo "- $dir/<br>";
}
?>
<?php
/**
* 递归扫描目录,返回所有文件和目录的列表
* @param string $directory 起始目录
* @param bool $includeDirs 是否在结果中包含目录
* @return array 所有文件和目录的数组
*/
function scanDirectoryRecursive($directory, $includeDirs = true) {
$result = [];
// 扫描当前目录
$items = scandir($directory);
if ($items === false) {
return $result;
}
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$fullPath = $directory . DIRECTORY_SEPARATOR . $item;
if (is_dir($fullPath)) {
// 如果是目录,递归扫描
if ($includeDirs) {
$result[] = $fullPath;
}
$result = array_merge($result, scanDirectoryRecursive($fullPath, $includeDirs));
} else {
// 如果是文件,添加到结果
$result[] = $fullPath;
}
}
return $result;
}
/**
* 递归扫描目录,按类型分类
* @param string $directory 起始目录
* @return array 分类后的结果
*/
function scanDirectoryByType($directory) {
$result = [
'files' => [],
'directories' => []
];
$items = scandir($directory);
if ($items === false) {
return $result;
}
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$fullPath = $directory . DIRECTORY_SEPARATOR . $item;
if (is_dir($fullPath)) {
$result['directories'][] = $fullPath;
// 递归扫描子目录
$subResult = scanDirectoryByType($fullPath);
$result['files'] = array_merge($result['files'], $subResult['files']);
$result['directories'] = array_merge($result['directories'], $subResult['directories']);
} else {
$result['files'][] = $fullPath;
}
}
return $result;
}
// 使用示例
$startDir = "/var/www";
echo "递归扫描目录: $startDir<br><br>";
// 方法1:所有文件和目录
$allItems = scanDirectoryRecursive($startDir);
echo "所有项目 (" . count($allItems) . "):<br>";
foreach (array_slice($allItems, 0, 10) as $item) { // 只显示前10个
echo "- $item<br>";
}
if (count($allItems) > 10) {
echo "... 还有 " . (count($allItems) - 10) . " 个项目<br>";
}
echo "<br>";
// 方法2:按类型分类
$categorized = scanDirectoryByType($startDir);
echo "文件总数: " . count($categorized['files']) . "<br>";
echo "目录总数: " . count($categorized['directories']) . "<br>";
?>
<?php
// 访问FTP服务器的示例
$ftpUrl = "ftp://example.com/public_html/";
// 创建FTP上下文选项
$options = [
'ftp' => [
'username' => 'ftp_user',
'password' => 'ftp_password',
'timeout' => 30
]
];
// 创建流上下文
$context = stream_context_create($options);
// 使用上下文扫描FTP目录
try {
$files = @scandir($ftpUrl, SCANDIR_SORT_ASCENDING, $context);
if ($files !== false) {
echo "FTP目录内容:<br>";
foreach ($files as $file) {
echo htmlspecialchars($file) . "<br>";
}
} else {
echo "无法访问FTP目录,请检查连接参数";
}
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
// 另一个示例:使用自定义HTTP头访问Web目录(如果支持)
$httpUrl = "http://example.com/directory/";
$httpOptions = [
'http' => [
'method' => 'GET',
'header' => "User-Agent: Mozilla/5.0\r\n"
]
];
$httpContext = stream_context_create($httpOptions);
// 注意:大多数HTTP服务器不返回目录列表,这里只是示例
?>
<?php
/**
* 获取目录的详细统计信息
* @param string $directory 目录路径
* @return array 统计信息
*/
function getDirectoryStats($directory) {
$stats = [
'total_files' => 0,
'total_dirs' => 0,
'total_size' => 0,
'file_types' => [],
'largest_file' => ['name' => '', 'size' => 0],
'newest_file' => ['name' => '', 'time' => 0],
'oldest_file' => ['name' => '', 'time' => PHP_INT_MAX]
];
$items = scandir($directory);
if ($items === false) {
return $stats;
}
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$fullPath = $directory . '/' . $item;
if (is_dir($fullPath)) {
$stats['total_dirs']++;
// 递归统计子目录
$subStats = getDirectoryStats($fullPath);
$stats['total_files'] += $subStats['total_files'];
$stats['total_dirs'] += $subStats['total_dirs'];
$stats['total_size'] += $subStats['total_size'];
// 合并文件类型统计
foreach ($subStats['file_types'] as $type => $count) {
if (!isset($stats['file_types'][$type])) {
$stats['file_types'][$type] = 0;
}
$stats['file_types'][$type] += $count;
}
// 更新最大文件等信息
if ($subStats['largest_file']['size'] > $stats['largest_file']['size']) {
$stats['largest_file'] = $subStats['largest_file'];
}
} elseif (is_file($fullPath)) {
$stats['total_files']++;
$fileSize = filesize($fullPath);
$stats['total_size'] += $fileSize;
// 文件类型统计
$extension = strtolower(pathinfo($item, PATHINFO_EXTENSION));
if (empty($extension)) {
$extension = '无扩展名';
}
if (!isset($stats['file_types'][$extension])) {
$stats['file_types'][$extension] = 0;
}
$stats['file_types'][$extension]++;
// 更新最大文件
if ($fileSize > $stats['largest_file']['size']) {
$stats['largest_file'] = ['name' => $fullPath, 'size' => $fileSize];
}
// 更新最新/最旧文件
$modTime = filemtime($fullPath);
if ($modTime > $stats['newest_file']['time']) {
$stats['newest_file'] = ['name' => $fullPath, 'time' => $modTime];
}
if ($modTime < $stats['oldest_file']['time']) {
$stats['oldest_file'] = ['name' => $fullPath, 'time' => $modTime];
}
}
}
return $stats;
}
/**
* 格式化字节大小
* @param int $bytes 字节数
* @param int $precision 小数位数
* @return string 格式化后的字符串
*/
function formatBytes($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
// 使用示例
$directory = "/var/www";
$stats = getDirectoryStats($directory);
echo "<h4>目录统计: $directory</h4>";
echo "文件总数: " . number_format($stats['total_files']) . "<br>";
echo "目录总数: " . number_format($stats['total_dirs']) . "<br>";
echo "总大小: " . formatBytes($stats['total_size']) . "<br><br>";
echo "最大文件: " . basename($stats['largest_file']['name']) .
" (" . formatBytes($stats['largest_file']['size']) . ")<br>";
echo "最新文件: " . basename($stats['newest_file']['name']) .
" (" . date('Y-m-d H:i:s', $stats['newest_file']['time']) . ")<br>";
echo "最旧文件: " . basename($stats['oldest_file']['name']) .
" (" . date('Y-m-d H:i:s', $stats['oldest_file']['time']) . ")<br><br>";
echo "文件类型分布:<br>";
arsort($stats['file_types']); // 按数量降序排序
foreach ($stats['file_types'] as $type => $count) {
$percentage = round(($count / $stats['total_files']) * 100, 2);
echo "$type: $count 个文件 ($percentage%)<br>";
}
?>
| 特性 | scandir() | opendir()/readdir() |
|---|---|---|
| 易用性 | 简单,一次调用返回所有结果 | 复杂,需要多个函数配合 |
| 内存使用 | 高(一次性加载所有条目) | 低(一次读取一个条目) |
| 性能 | 适合小型目录 | 适合大型目录,可提前中断 |
| 控制能力 | 低(必须处理整个数组) | 高(可以控制读取过程) |
| 资源管理 | 自动管理,无显式关闭 | 需要手动打开和关闭 |
| 排序选项 | 内置排序功能 | 需要手动排序 |
| 推荐场景 | 小型目录、简单列表、需要排序 | 大型目录、流式处理、需要提前中断 |
| 特性 | scandir() | glob() |
|---|---|---|
| 返回内容 | 所有文件和目录 | 匹配模式的文件/目录 |
| 过滤能力 | 无内置过滤,需手动过滤 | 支持通配符模式过滤 |
| 排序选项 | 支持升序、降序、不排序 | 返回结果已排序 |
| 递归支持 | 不支持递归(需手动实现) | 不支持递归(但有递归扩展) |
| 性能 | 通常比glob()稍快 | 模式匹配有额外开销 |
| 使用场景 | 需要所有条目、需要排序控制 | 需要模式匹配、通配符过滤 |
. 和 .. 这两个特殊目录SCANDIR_SORT_NONE 提高性能,或使用 opendir()/readdir()| 错误 | 原因 | 解决方法 |
|---|---|---|
| Warning: scandir(): (errno 2): No such file or directory | 目录不存在 | 检查目录路径是否正确,确保目录存在 |
| Warning: scandir(): failed to open dir: Permission denied | 没有读取目录的权限 | 检查目录权限,或使用适当权限运行脚本 |
| 内存耗尽错误 | 目录包含大量文件,一次性加载导致内存不足 | 使用 opendir()/readdir() 或分批处理 |
| 包含不需要的特殊条目 | 没有过滤 . 和 .. |
在循环中过滤这些特殊条目 |
| 性能问题 | 对大型目录使用默认排序 | 使用 SCANDIR_SORT_NONE 或 opendir()/readdir() |
当不需要排序时,使用 SCANDIR_SORT_NONE 可以显著提高性能,特别是在处理大型目录时。
对于非常大的目录,考虑使用 opendir()/readdir() 进行流式处理,避免内存问题。
如果目录内容不经常变化,可以考虑缓存扫描结果,避免重复扫描。
对于复杂的目录遍历需求,考虑使用 DirectoryIterator 或 RecursiveDirectoryIterator。