PHP rewinddir() 函数

定义和用法

rewinddir() 函数用于将目录句柄的指针重置到目录的开头。这使得可以重新读取目录内容,而不需要关闭并重新打开目录句柄。

当使用 readdir() 函数遍历目录后,目录指针会指向末尾。如果想再次遍历同一个目录,可以使用 rewinddir() 将指针重置到开头,然后再次使用 readdir() 读取。

注意:rewinddir() 函数只重置目录指针,不会改变目录句柄的状态。这意味着它可以在任何时候调用,无论目录指针当前处于什么位置。

语法

rewinddir(?resource $dir_handle = null): void

参数

参数 描述
dir_handle

可选。由 opendir() 打开的目录句柄资源。

如果省略此参数或设置为 null,函数将重置最后打开的目录句柄的指针。

从 PHP 8.0.0 开始,此参数可以为 null。

返回值

rewinddir() 函数没有返回值(返回 void)。

目录指针工作原理

1
opendir()
打开目录
2
readdir()
读取条目
3
rewinddir()
重置指针
4
readdir()
重新读取

示例

示例1:基本用法

<?php
// 打开目录
$dir = "/tmp";
$handle = opendir($dir);

if ($handle) {
    echo "第一次遍历目录内容:<br>";
    $count1 = 0;

    // 读取前3个条目
    while (false !== ($entry = readdir($handle)) && $count1 < 3) {
        echo $entry . "<br>";
        $count1++;
    }

    echo "<br>只显示了前 $count1 个条目<br><br>";

    // 重置目录指针
    rewinddir($handle);
    echo "已使用 rewinddir() 重置指针<br><br>";

    echo "第二次遍历所有目录内容:<br>";
    $count2 = 0;
    while (false !== ($entry = readdir($handle))) {
        echo $entry . "<br>";
        $count2++;
    }

    echo "<br>总计 $count2 个条目<br>";

    // 关闭目录句柄
    closedir($handle);
} else {
    echo "无法打开目录: $dir";
}
?>

示例2:多次重置和重新读取

<?php
/**
 * 演示多次使用 rewinddir() 重新读取目录内容
 */
function demonstrateRewinddir($directory) {
    $handle = opendir($directory);

    if (!$handle) {
        return "无法打开目录: $directory";
    }

    $output = "目录: $directory<br><br>";

    // 第一次读取:获取所有文件
    $allFiles = [];
    while (false !== ($entry = readdir($handle))) {
        if ($entry !== '.' && $entry !== '..') {
            $allFiles[] = $entry;
        }
    }
    $output .= "第一次读取: 找到 " . count($allFiles) . " 个文件/目录<br>";

    // 重置指针
    rewinddir($handle);
    $output .= "已重置指针<br><br>";

    // 第二次读取:只获取目录
    $directories = [];
    while (false !== ($entry = readdir($handle))) {
        if ($entry !== '.' && $entry !== '..') {
            $fullPath = $directory . '/' . $entry;
            if (is_dir($fullPath)) {
                $directories[] = $entry;
            }
        }
    }
    $output .= "第二次读取: 找到 " . count($directories) . " 个子目录<br>";

    // 再次重置指针
    rewinddir($handle);
    $output .= "再次重置指针<br><br>";

    // 第三次读取:只获取特定扩展名的文件
    $phpFiles = [];
    while (false !== ($entry = readdir($handle))) {
        if ($entry !== '.' && $entry !== '..') {
            $fullPath = $directory . '/' . $entry;
            if (is_file($fullPath) && pathinfo($entry, PATHINFO_EXTENSION) === 'php') {
                $phpFiles[] = $entry;
            }
        }
    }
    $output .= "第三次读取: 找到 " . count($phpFiles) . " 个PHP文件<br><br>";

    // 显示详细结果
    $output .= "详细结果:<br>";
    $output .= "所有文件/目录: " . implode(', ', $allFiles) . "<br>";
    $output .= "子目录: " . implode(', ', $directories) . "<br>";
    $output .= "PHP文件: " . implode(', ', $phpFiles) . "<br>";

    closedir($handle);
    return $output;
}

// 使用示例
echo demonstrateRewinddir("/tmp");
?>

示例3:在循环中使用 rewinddir()

<?php
/**
 * 使用 rewinddir() 实现目录内容的分页显示
 * @param string $directory 目录路径
 * @param int $page 当前页码
 * @param int $perPage 每页显示数量
 * @return array 分页数据
 */
function paginateDirectory($directory, $page = 1, $perPage = 10) {
    $handle = opendir($directory);

    if (!$handle) {
        return ['error' => "无法打开目录: $directory"];
    }

    $allEntries = [];

    // 第一次遍历:获取所有条目(排除 . 和 ..)
    while (false !== ($entry = readdir($handle))) {
        if ($entry !== '.' && $entry !== '..') {
            $allEntries[] = $entry;
        }
    }

    // 计算分页信息
    $total = count($allEntries);
    $totalPages = ceil($total / $perPage);
    $page = max(1, min($page, $totalPages));
    $offset = ($page - 1) * $perPage;

    // 重置指针
    rewinddir($handle);

    // 跳过不需要的条目
    $skipped = 0;
    $currentEntries = [];
    $currentIndex = 0;

    while (false !== ($entry = readdir($handle))) {
        if ($entry === '.' || $entry === '..') {
            continue;
        }

        if ($skipped < $offset) {
            $skipped++;
            continue;
        }

        if (count($currentEntries) < $perPage) {
            $currentEntries[] = $entry;
        } else {
            break;
        }

        $currentIndex++;
    }

    closedir($handle);

    return [
        'entries' => $currentEntries,
        'page' => $page,
        'perPage' => $perPage,
        'total' => $total,
        'totalPages' => $totalPages,
        'hasPrevious' => $page > 1,
        'hasNext' => $page < $totalPages
    ];
}

// 使用示例
$directory = "/tmp";
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$result = paginateDirectory($directory, $page, 5);

if (isset($result['error'])) {
    echo $result['error'];
} else {
    echo "目录内容 (第 {$result['page']} 页,共 {$result['totalPages']} 页):<br><br>";

    foreach ($result['entries'] as $entry) {
        echo "- $entry<br>";
    }

    echo "<br>";
    echo "总计: {$result['total']} 个条目<br>";

    // 分页导航
    if ($result['hasPrevious']) {
        echo "<a href='?page=" . ($result['page'] - 1) . "'>上一页</a> | ";
    }

    for ($i = 1; $i <= $result['totalPages']; $i++) {
        if ($i == $result['page']) {
            echo "<strong>$i</strong> ";
        } else {
            echo "<a href='?page=$i'>$i</a> ";
        }
    }

    if ($result['hasNext']) {
        echo "| <a href='?page=" . ($result['page'] + 1) . "'>下一页</a>";
    }
}
?>

示例4:Directory 类的 rewind() 方法

<?php
// 使用 dir() 函数返回的 Directory 对象
$dir = dir("/tmp");

if ($dir) {
    echo "使用 Directory 对象的 rewind() 方法:<br><br>";

    echo "第一次读取:<br>";
    $count = 0;
    while (false !== ($entry = $dir->read()) && $count < 5) {
        echo $entry . "<br>";
        $count++;
    }

    echo "<br>已读取 $count 个条目<br><br>";

    // 使用 Directory 对象的 rewind() 方法
    $dir->rewind();
    echo "已调用 rewind() 方法重置指针<br><br>";

    echo "第二次读取(所有条目):<br>";
    $total = 0;
    while (false !== ($entry = $dir->read())) {
        echo $entry . "<br>";
        $total++;
    }

    echo "<br>总计: $total 个条目<br>";

    $dir->close();
} else {
    echo "无法打开目录";
}
?>

示例5:错误处理和资源管理

<?php
/**
 * 安全的目录遍历和重置函数
 */
function safelyRewindDirectory($directory) {
    // 尝试打开目录
    $handle = @opendir($directory);

    if ($handle === false) {
        $error = error_get_last();
        return "错误: 无法打开目录 '$directory' - " . $error['message'];
    }

    try {
        $results = [];

        // 第一次遍历:获取文件统计
        echo "第一次遍历 - 获取基本信息:<br>";
        $fileCount = 0;
        $dirCount = 0;

        while (false !== ($entry = readdir($handle))) {
            if ($entry === '.' || $entry === '..') {
                continue;
            }

            $fullPath = $directory . DIRECTORY_SEPARATOR . $entry;

            if (is_file($fullPath)) {
                $fileCount++;
            } elseif (is_dir($fullPath)) {
                $dirCount++;
            }
        }

        $results['first_pass'] = ['files' => $fileCount, 'directories' => $dirCount];
        echo "文件: $fileCount, 目录: $dirCount<br><br>";

        // 重置指针
        if (!rewinddir($handle)) {
            throw new Exception("无法重置目录指针");
        }

        echo "已成功重置目录指针<br><br>";

        // 第二次遍历:获取详细文件列表
        echo "第二次遍历 - 获取详细列表:<br>";
        $details = [];

        while (false !== ($entry = readdir($handle))) {
            if ($entry === '.' || $entry === '..') {
                continue;
            }

            $fullPath = $directory . DIRECTORY_SEPARATOR . $entry;

            $details[] = [
                'name' => $entry,
                'type' => is_dir($fullPath) ? 'directory' : 'file',
                'size' => is_file($fullPath) ? filesize($fullPath) : null,
                'modified' => date('Y-m-d H:i:s', filemtime($fullPath))
            ];
        }

        $results['second_pass'] = $details;

        // 输出详细信息
        foreach ($details as $item) {
            $size = $item['size'] ? formatBytes($item['size']) : '-';
            echo "{$item['name']} ({$item['type']}, {$size}, {$item['modified']})<br>";
        }

        return $results;

    } catch (Exception $e) {
        return "错误: " . $e->getMessage();
    } finally {
        // 确保关闭目录句柄
        if ($handle && is_resource($handle)) {
            closedir($handle);
            echo "<br>目录句柄已关闭";
        }
    }
}

// 辅助函数:格式化字节大小
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];
}

// 使用示例
$result = safelyRewindDirectory("/tmp");
if (is_string($result)) {
    echo $result; // 显示错误信息
}
?>

rewinddir() vs 关闭重新打开的性能对比

<?php
/**
 * 比较 rewinddir() 和关闭重新打开的性能
 */
function comparePerformance($directory, $iterations = 1000) {
    $results = [];

    // 方法1:使用 rewinddir()
    $start1 = microtime(true);

    $handle = opendir($directory);
    if ($handle) {
        for ($i = 0; $i < $iterations; $i++) {
            while (false !== ($entry = readdir($handle))) {
                // 模拟一些处理
                strlen($entry);
            }
            rewinddir($handle);
        }
        closedir($handle);
    }

    $end1 = microtime(true);
    $results['rewinddir'] = $end1 - $start1;

    // 方法2:关闭并重新打开
    $start2 = microtime(true);

    for ($i = 0; $i < $iterations; $i++) {
        $handle = opendir($directory);
        if ($handle) {
            while (false !== ($entry = readdir($handle))) {
                // 模拟一些处理
                strlen($entry);
            }
            closedir($handle);
        }
    }

    $end2 = microtime(true);
    $results['reopen'] = $end2 - $start2;

    return $results;
}

// 运行性能测试(小规模测试)
echo "性能测试(100次迭代):<br>";
$performance = comparePerformance("/tmp", 100);

echo "使用 rewinddir(): " . round($performance['rewinddir'] * 1000, 2) . " 毫秒<br>";
echo "关闭并重新打开: " . round($performance['reopen'] * 1000, 2) . " 毫秒<br><br>";

$difference = $performance['reopen'] - $performance['rewinddir'];
$percentage = ($difference / $performance['rewinddir']) * 100;

echo "性能差异: " . round($difference * 1000, 2) . " 毫秒<br>";
echo "rewinddir() 比关闭重新打开快 " . round($percentage, 2) . "%";
?>

常见使用场景

分页显示目录内容

使用 rewinddir() 可以方便地实现目录内容的分页,避免多次打开目录的开销。

多次处理相同目录

当需要对同一个目录进行多次不同的处理(如统计、过滤、排序)时,使用 rewinddir() 可以提高效率。

调试和开发

在开发过程中,可以使用 rewinddir() 重置指针,多次测试不同的目录处理逻辑。

资源受限环境

在资源受限的环境中,使用 rewinddir() 避免频繁的目录打开/关闭操作,节省系统资源。

最佳实践

  1. 检查句柄有效性:在调用 rewinddir() 之前,确保目录句柄是有效的资源
  2. 错误处理:虽然 rewinddir() 不会返回错误,但应该处理可能出现的异常情况
  3. 资源管理:使用完成后记得调用 closedir() 关闭目录句柄
  4. 性能考虑:对于需要多次遍历的大型目录,使用 rewinddir() 比关闭重新打开更高效
  5. 编码一致性:在整个应用程序中保持一致的目录处理模式
  6. 与现代方法结合:考虑使用 DirectoryIterator 等现代 OOP 方法,它们提供了更简洁的 API

注意事项

重要:
  • rewinddir() 只能重置由 opendir() 打开的目录句柄的指针
  • 如果目录内容在两次读取之间发生了变化,rewinddir() 不会更新目录内容列表
  • rewinddir() 不会影响 Directory 对象的状态,Directory 对象有自己独立的 rewind() 方法
  • 在多线程环境中使用目录句柄时需要小心同步问题

相关函数