PHPfileinode()函数

fileinode() 函数是PHP中用于获取文件inode编号的内置函数。inode(索引节点)是Unix/Linux文件系统中的基本概念,每个文件和目录都有唯一的inode编号,存储着文件的元数据信息。

什么是inode?

inode(index node)是Unix/Linux文件系统中用于存储文件元数据的数据结构。每个inode都有一个唯一的编号,包含文件的权限、所有者、大小、时间戳等信息,但不包含文件名。文件名存储在目录项中,指向相应的inode。

语法

int fileinode ( string $filename )

参数说明

参数 描述 类型 是否必需
filename 要检查的文件路径 string

返回值

  • 成功时返回文件的inode编号(正整数)
  • 失败时返回 FALSE
  • 在Windows系统上,通常返回0或可能不可靠的结果

注意事项

  • 此函数主要适用于Unix/Linux系统,Windows系统支持有限
  • inode编号在同一文件系统内是唯一的
  • 当文件被删除或文件系统被格式化时,inode编号可能会被重用
  • 硬链接共享相同的inode编号
  • 符号链接有自己的inode编号,与其指向的文件不同
  • 需要文件读取权限才能获取inode信息

示例代码

示例1:基本使用 - 获取文件inode编号
<?php
$filename = 'test.txt';

// 获取文件的inode编号
$inode = fileinode($filename);

if ($inode !== false) {
    echo "文件 {$filename} 的inode编号是: " . $inode . "<br>";
} else {
    echo "无法获取文件inode编号";
}
?>
示例2:比较文件的inode信息
<?php
function getFileInodeInfo($filename) {
    if (!file_exists($filename)) {
        return "文件不存在: {$filename}";
    }

    // 清除文件状态缓存
    clearstatcache(true, $filename);

    // 获取inode编号
    $inode = fileinode($filename);
    if ($inode === false) {
        return "无法获取inode编号";
    }

    // 获取其他文件信息
    $uid = fileowner($filename);
    $gid = filegroup($filename);
    $perms = fileperms($filename);
    $size = filesize($filename);
    $ctime = filectime($filename);
    $mtime = filemtime($filename);
    $atime = fileatime($filename);

    // 检查是否为符号链接
    $is_link = is_link($filename);

    // 如果是符号链接,获取目标文件的inode
    $target_inode = null;
    if ($is_link) {
        $target = readlink($filename);
        if ($target !== false && file_exists($target)) {
            $target_inode = fileinode($target);
        }
    }

    return [
        'filename' => $filename,
        'inode' => $inode,
        'is_symbolic_link' => $is_link,
        'target_inode' => $target_inode,
        'file_size' => $size,
        'owner_id' => $uid,
        'group_id' => $gid,
        'permissions' => substr(sprintf('%o', $perms), -4),
        'ctime' => date('Y-m-d H:i:s', $ctime), // inode修改时间
        'mtime' => date('Y-m-d H:i:s', $mtime), // 内容修改时间
        'atime' => date('Y-m-d H:i:s', $atime)  // 访问时间
    ];
}

// 使用示例
$info = getFileInodeInfo('/etc/passwd');
if (is_array($info)) {
    echo "<h4>文件inode信息:</h4>";
    echo "文件名: {$info['filename']}<br>";
    echo "inode编号: {$info['inode']}<br>";
    echo "文件大小: " . number_format($info['file_size']) . " 字节<br>";
    echo "权限: {$info['permissions']}<br>";
    echo "inode修改时间: {$info['ctime']}<br>";
    echo "内容修改时间: {$info['mtime']}<br>";

    if ($info['is_symbolic_link']) {
        echo "这是一个符号链接<br>";
        if ($info['target_inode']) {
            echo "目标文件inode: {$info['target_inode']}<br>";
        }
    }
} else {
    echo $info;
}
?>
示例3:检测硬链接
<?php
class HardLinkDetector {

    public function findHardLinks($directory) {
        if (!is_dir($directory)) {
            return ['error' => "目录不存在: {$directory}"];
        }

        $inode_map = [];
        $hard_links = [];

        // 遍历目录
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $file) {
            if ($file->isFile() && !$file->isLink()) {
                $filepath = $file->getPathname();
                $inode = fileinode($filepath);

                if ($inode !== false) {
                    if (!isset($inode_map[$inode])) {
                        $inode_map[$inode] = [];
                    }
                    $inode_map[$inode][] = $filepath;
                }
            }
        }

        // 找出有多个文件共享的inode(硬链接)
        foreach ($inode_map as $inode => $files) {
            if (count($files) > 1) {
                $hard_links[] = [
                    'inode' => $inode,
                    'link_count' => count($files),
                    'files' => $files,
                    'size' => filesize($files[0])
                ];
            }
        }

        return [
            'directory' => $directory,
            'total_files_scanned' => count($inode_map),
            'hard_links_found' => count($hard_links),
            'hard_links' => $hard_links
        ];
    }

    public function createHardLinkReport($directory) {
        $result = $this->findHardLinks($directory);

        if (isset($result['error'])) {
            return $result;
        }

        $report = "硬链接检测报告\n";
        $report .= "=" . str_repeat("=", 50) . "\n";
        $report .= "目录: {$result['directory']}\n";
        $report .= "扫描文件数: {$result['total_files_scanned']}\n";
        $report .= "发现的硬链接组数: {$result['hard_links_found']}\n\n";

        foreach ($result['hard_links'] as $index => $link_group) {
            $report .= "硬链接组 #" . ($index + 1) . "\n";
            $report .= "inode编号: {$link_group['inode']}\n";
            $report .= "链接数: {$link_group['link_count']}\n";
            $report .= "文件大小: " . number_format($link_group['size']) . " 字节\n";
            $report .= "文件列表:\n";

            foreach ($link_group['files'] as $file) {
                $report .= "  - {$file}\n";
            }
            $report .= "\n";
        }

        // 保存报告
        $report_file = "hardlink_report_" . date('Ymd_His') . ".txt";
        file_put_contents($report_file, $report);

        return [
            'status' => 'success',
            'report_file' => $report_file,
            'summary' => [
                'directory' => $result['directory'],
                'total_files' => $result['total_files_scanned'],
                'hard_link_groups' => $result['hard_links_found']
            ]
        ];
    }
}

// 使用示例
$detector = new HardLinkDetector();
$result = $detector->findHardLinks('/var/www/html');

if (!isset($result['error'])) {
    echo "<h4>硬链接检测结果:</h4>";
    echo "扫描目录: {$result['directory']}<br>";
    echo "扫描文件数: {$result['total_files_scanned']}<br>";
    echo "发现硬链接组数: {$result['hard_links_found']}<br>";

    if ($result['hard_links_found'] > 0) {
        echo "<h5>硬链接组:</h5>";
        foreach ($result['hard_links'] as $index => $link_group) {
            echo "<strong>组 #" . ($index + 1) . " (inode: {$link_group['inode']})</strong><br>";
            echo "链接数: {$link_group['link_count']}, 大小: " . number_format($link_group['size']) . " 字节<br>";
            echo "文件:<br>";
            foreach ($link_group['files'] as $file) {
                echo "  - {$file}<br>";
            }
            echo "<br>";
        }

        // 生成详细报告
        $report_result = $detector->createHardLinkReport('/var/www/html');
        if ($report_result['status'] === 'success') {
            echo "<div class='alert alert-info'>详细报告已保存到: {$report_result['report_file']}</div>";
        }
    }
} else {
    echo "错误: " . $result['error'];
}
?>
示例4:inode使用情况监控
<?php
class InodeUsageMonitor {

    public function getInodeUsage($directory) {
        if (!is_dir($directory)) {
            return ['error' => "目录不存在: {$directory}"];
        }

        $inode_stats = [
            'total_inodes' => 0,
            'unique_inodes' => 0,
            'hard_links' => 0,
            'symbolic_links' => 0,
            'regular_files' => 0,
            'directories' => 0,
            'inode_distribution' => [],
            'largest_files' => []
        ];

        $inode_map = [];
        $file_sizes = [];

        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $file) {
            if ($file->isDot()) {
                continue;
            }

            $inode_stats['total_inodes']++;

            if ($file->isDir()) {
                $inode_stats['directories']++;
            } elseif ($file->isLink()) {
                $inode_stats['symbolic_links']++;
            } elseif ($file->isFile()) {
                $inode_stats['regular_files']++;

                $filepath = $file->getPathname();
                $inode = fileinode($filepath);
                $size = $file->getSize();

                if ($inode !== false) {
                    if (!isset($inode_map[$inode])) {
                        $inode_map[$inode] = 0;
                    }
                    $inode_map[$inode]++;

                    // 记录文件大小用于排序
                    $file_sizes[] = [
                        'path' => $filepath,
                        'size' => $size,
                        'inode' => $inode
                    ];
                }
            }
        }

        // 分析inode分布
        $inode_stats['unique_inodes'] = count($inode_map);

        foreach ($inode_map as $inode => $count) {
            if ($count > 1) {
                $inode_stats['hard_links'] += ($count - 1);
            }

            // 统计链接数分布
            if (!isset($inode_stats['inode_distribution'][$count])) {
                $inode_stats['inode_distribution'][$count] = 0;
            }
            $inode_stats['inode_distribution'][$count]++;
        }

        // 获取最大的文件
        usort($file_sizes, function($a, $b) {
            return $b['size'] - $a['size'];
        });

        $inode_stats['largest_files'] = array_slice($file_sizes, 0, 10);

        return $inode_stats;
    }

    public function getInodeEfficiency($directory) {
        $stats = $this->getInodeUsage($directory);

        if (isset($stats['error'])) {
            return $stats;
        }

        if ($stats['total_inodes'] === 0) {
            return ['error' => '没有找到文件'];
        }

        $efficiency = ($stats['unique_inodes'] / $stats['total_inodes']) * 100;

        return [
            'directory' => $directory,
            'total_entries' => $stats['total_inodes'],
            'unique_inodes' => $stats['unique_inodes'],
            'inode_efficiency' => round($efficiency, 2),
            'hard_link_savings' => $stats['total_inodes'] - $stats['unique_inodes'],
            'breakdown' => [
                'directories' => $stats['directories'],
                'regular_files' => $stats['regular_files'],
                'symbolic_links' => $stats['symbolic_links'],
                'hard_links' => $stats['hard_links']
            ],
            'distribution' => $stats['inode_distribution'],
            'largest_files' => $stats['largest_files']
        ];
    }
}

// 使用示例
$monitor = new InodeUsageMonitor();
$result = $monitor->getInodeEfficiency('/var/log');

if (!isset($result['error'])) {
    echo "<h4>inode使用效率报告:</h4>";
    echo "目录: {$result['directory']}<br>";
    echo "总条目数: " . number_format($result['total_entries']) . "<br>";
    echo "唯一inode数: " . number_format($result['unique_inodes']) . "<br>";
    echo "inode使用效率: {$result['inode_efficiency']}%<br>";
    echo "硬链接节省的inode数: " . number_format($result['hard_link_savings']) . "<br>";

    echo "<h5>条目分类:</h5>";
    echo "目录: " . number_format($result['breakdown']['directories']) . "<br>";
    echo "普通文件: " . number_format($result['breakdown']['regular_files']) . "<br>";
    echo "符号链接: " . number_format($result['breakdown']['symbolic_links']) . "<br>";
    echo "硬链接: " . number_format($result['breakdown']['hard_links']) . "<br>";

    echo "<h5>inode链接数分布:</h5>";
    ksort($result['distribution']);
    foreach ($result['distribution'] as $links => $count) {
        echo "{$links}个链接: {$count}个inode<br>";
    }

    echo "<h5>最大的文件:</h5>";
    foreach ($result['largest_files'] as $index => $file) {
        $size_mb = round($file['size'] / (1024 * 1024), 2);
        echo ($index + 1) . ". {$file['path']} - {$size_mb} MB (inode: {$file['inode']})<br>";
    }
} else {
    echo "错误: " . $result['error'];
}
?>
示例5:文件系统完整性检查
<?php
class FilesystemIntegrityChecker {
    private $baseline_file = 'filesystem_baseline.json';

    public function createBaseline($directory) {
        if (!is_dir($directory)) {
            return ['error' => "目录不存在: {$directory}"];
        }

        $baseline = [
            'created' => time(),
            'directory' => $directory,
            'files' => []
        ];

        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $file) {
            if ($file->isDot()) {
                continue;
            }

            $filepath = $file->getPathname();
            $inode = fileinode($filepath);

            if ($inode !== false) {
                $baseline['files'][$filepath] = [
                    'inode' => $inode,
                    'size' => $file->getSize(),
                    'mtime' => filemtime($filepath),
                    'ctime' => filectime($filepath),
                    'atime' => fileatime($filepath),
                    'perms' => fileperms($filepath),
                    'owner' => fileowner($filepath),
                    'group' => filegroup($filepath),
                    'type' => $this->getFileType($file),
                    'hash' => $file->isFile() ? md5_file($filepath) : null
                ];
            }
        }

        // 按inode排序,便于比较
        uasort($baseline['files'], function($a, $b) {
            return $a['inode'] - $b['inode'];
        });

        // 保存基线
        file_put_contents(
            $this->baseline_file,
            json_encode($baseline, JSON_PRETTY_PRINT)
        );

        return [
            'status' => 'success',
            'message' => '基线已创建',
            'baseline_file' => $this->baseline_file,
            'files_count' => count($baseline['files'])
        ];
    }

    public function checkIntegrity() {
        if (!file_exists($this->baseline_file)) {
            return ['error' => '基线文件不存在'];
        }

        $baseline = json_decode(file_get_contents($this->baseline_file), true);
        $current_state = [];
        $changes = [];
        $missing_files = [];
        $new_files = [];

        $directory = $baseline['directory'];

        if (!is_dir($directory)) {
            return ['error' => "目录不存在: {$directory}"];
        }

        // 获取当前状态
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $file) {
            if ($file->isDot()) {
                continue;
            }

            $filepath = $file->getPathname();
            $inode = fileinode($filepath);

            if ($inode !== false) {
                $current_state[$filepath] = [
                    'inode' => $inode,
                    'size' => $file->getSize(),
                    'mtime' => filemtime($filepath),
                    'ctime' => filectime($filepath),
                    'type' => $this->getFileType($file)
                ];
            }
        }

        // 检查缺失的文件
        foreach ($baseline['files'] as $filepath => $baseline_data) {
            if (!isset($current_state[$filepath])) {
                $missing_files[$filepath] = $baseline_data;
                continue;
            }

            $current_data = $current_state[$filepath];
            $inode_changed = $baseline_data['inode'] != $current_data['inode'];
            $size_changed = $baseline_data['size'] != $current_data['size'];
            $mtime_changed = $baseline_data['mtime'] != $current_data['mtime'];

            if ($inode_changed || $size_changed || $mtime_changed) {
                $changes[$filepath] = [
                    'baseline' => $baseline_data,
                    'current' => $current_data,
                    'inode_changed' => $inode_changed,
                    'size_changed' => $size_changed,
                    'mtime_changed' => $mtime_changed
                ];
            }
        }

        // 检查新增的文件
        foreach ($current_state as $filepath => $current_data) {
            if (!isset($baseline['files'][$filepath])) {
                $new_files[$filepath] = $current_data;
            }
        }

        return [
            'status' => 'success',
            'baseline_created' => date('Y-m-d H:i:s', $baseline['created']),
            'directory' => $directory,
            'summary' => [
                'baseline_files' => count($baseline['files']),
                'current_files' => count($current_state),
                'missing_files' => count($missing_files),
                'changed_files' => count($changes),
                'new_files' => count($new_files)
            ],
            'details' => [
                'missing_files' => $missing_files,
                'changed_files' => $changes,
                'new_files' => $new_files
            ]
        ];
    }

    private function getFileType($file) {
        if ($file->isDir()) {
            return 'directory';
        } elseif ($file->isLink()) {
            return 'link';
        } elseif ($file->isFile()) {
            return 'file';
        }
        return 'unknown';
    }
}

// 使用示例
$checker = new FilesystemIntegrityChecker();

// 创建基线(首次运行)
// $result = $checker->createBaseline('/etc');
// if ($result['status'] === 'success') {
//     echo "基线创建成功: {$result['baseline_file']}<br>";
//     echo "记录了 {$result['files_count']} 个文件<br>";
// }

// 检查完整性(后续运行)
$result = $checker->checkIntegrity();

if ($result['status'] === 'success') {
    echo "<h4>文件系统完整性检查:</h4>";
    echo "基线创建时间: {$result['baseline_created']}<br>";
    echo "检查目录: {$result['directory']}<br>";
    echo "<h5>摘要:</h5>";
    echo "基线文件数: " . number_format($result['summary']['baseline_files']) . "<br>";
    echo "当前文件数: " . number_format($result['summary']['current_files']) . "<br>";
    echo "缺失文件: " . number_format($result['summary']['missing_files']) . "<br>";
    echo "变更文件: " . number_format($result['summary']['changed_files']) . "<br>";
    echo "新增文件: " . number_format($result['summary']['new_files']) . "<br>";

    if ($result['summary']['missing_files'] > 0) {
        echo "<h5 class='text-danger'>缺失的文件:</h5>";
        foreach (array_slice($result['details']['missing_files'], 0, 5) as $filepath => $data) {
            echo "{$filepath} (inode: {$data['inode']})<br>";
        }
        if ($result['summary']['missing_files'] > 5) {
            echo "... 还有 " . ($result['summary']['missing_files'] - 5) . " 个文件<br>";
        }
    }

    if ($result['summary']['changed_files'] > 0) {
        echo "<h5 class='text-warning'>变更的文件 (前5个):</h5>";
        $count = 0;
        foreach ($result['details']['changed_files'] as $filepath => $change) {
            if (++$count > 5) break;

            echo "<strong>{$filepath}</strong><br>";
            if ($change['inode_changed']) {
                echo "  inode变更: {$change['baseline']['inode']} → {$change['current']['inode']}<br>";
            }
            if ($change['size_changed']) {
                echo "  大小变更: " . number_format($change['baseline']['size']) . " → " .
                     number_format($change['current']['size']) . " 字节<br>";
            }
            if ($change['mtime_changed']) {
                $old_time = date('Y-m-d H:i:s', $change['baseline']['mtime']);
                $new_time = date('Y-m-d H:i:s', $change['current']['mtime']);
                echo "  修改时间: {$old_time} → {$new_time}<br>";
            }
            echo "<br>";
        }
    }
} else {
    echo "错误: " . $result['error'];
}
?>

与类似函数的比较

函数 描述 返回内容 适用场景
fileinode() 获取文件inode编号 inode编号(整数) 检测硬链接、文件系统分析
stat() 获取文件所有状态信息 包含inode、大小、时间等的数组 需要完整文件信息
lstat() 获取文件或符号链接状态 与stat类似,但处理符号链接不同 处理符号链接的场景
fileowner() 获取文件所有者ID 用户ID(整数) 检查文件所有者
filegroup() 获取文件组ID 组ID(整数) 检查文件所属组

inode概念详解

inode基础知识
概念 描述 示例
inode编号 每个inode的唯一标识符 123456
inode内容 存储文件元数据,不包括文件名 权限、所有者、大小、时间戳等
硬链接 多个文件名指向同一个inode file1和file2共享inode
符号链接 特殊文件,包含目标文件路径 link1 → /path/to/file
链接计数 指向同一inode的文件名数量 链接计数为0时文件被删除

操作系统差异

跨平台注意事项
操作系统 fileinode() 行为 注意事项
Unix/Linux 返回有效的inode编号 正常工作,inode概念原生支持
Windows 通常返回0或不可靠结果 Windows使用不同的文件标识系统
macOS 与Unix/Linux行为一致 基于Unix系统,支持良好

性能优化建议

最佳实践
  • 使用clearstatcache()清除文件状态缓存,特别是在循环中调用时
  • 对于批量文件操作,考虑使用stat()一次性获取所有信息
  • 避免在大型目录树中频繁调用fileinode(),使用迭代器批量处理
  • 对于硬链接检测,缓存inode映射以提高性能
  • 在跨平台应用中,对Windows系统提供替代方案或提示
  • 使用绝对路径而不是相对路径,避免路径解析开销

常见问题解答

inode编号在同一文件系统内是唯一的,但在不同的文件系统之间可能重复。当文件被删除后,其inode编号可能被新文件重用。因此,inode编号不能作为文件的永久唯一标识符。

硬链接共享相同的inode编号,它们只是同一个文件的多个名称。
符号链接有自己的inode编号,它是一个特殊的文件,包含指向目标文件的路径。
示例:
$ ls -li
123456 -rw-r--r-- 2 user group 100 Jan 1 12:00 file1
123456 -rw-r--r-- 2 user group 100 Jan 1 12:00 file2  # 硬链接,相同inode
789012 lrwxrwxrwx 1 user group   5 Jan 1 12:00 link1 -> file1  # 符号链接,不同inode

在Windows系统中,可以使用以下方法获取文件唯一标识符:
1. 使用filectime()和文件路径组合作为唯一标识
2. 使用文件ID(File ID)和卷序列号(Volume Serial Number)
3. 使用第三方扩展或Windows API
示例:
// Windows替代方案
function getWindowsFileId($filename) {
    $ctime = filectime($filename);
    $size = filesize($filename);
    return md5($filename . $ctime . $size);
}
完整示例:文件去重工具
<?php
class FileDeduplicator {

    public function findDuplicateFiles($directory) {
        if (!is_dir($directory)) {
            return ['error' => "目录不存在: {$directory}"];
        }

        $file_map = [];
        $duplicates = [];

        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $file) {
            if ($file->isFile() && !$file->isLink()) {
                $filepath = $file->getPathname();
                $inode = fileinode($filepath);
                $size = $file->getSize();
                $hash = md5_file($filepath);

                $key = "{$size}:{$hash}";

                if (!isset($file_map[$key])) {
                    $file_map[$key] = [];
                }

                $file_map[$key][] = [
                    'path' => $filepath,
                    'inode' => $inode,
                    'size' => $size,
                    'mtime' => filemtime($filepath),
                    'is_hardlink' => false
                ];
            }
        }

        // 分析重复文件
        foreach ($file_map as $key => $files) {
            if (count($files) > 1) {
                // 检查是否有硬链接
                $inode_groups = [];
                foreach ($files as $file) {
                    $inode = $file['inode'];
                    if (!isset($inode_groups[$inode])) {
                        $inode_groups[$inode] = [];
                    }
                    $inode_groups[$inode][] = $file;
                }

                $duplicate_group = [
                    'size' => $files[0]['size'],
                    'total_copies' => count($files),
                    'unique_inodes' => count($inode_groups),
                    'potential_savings' => $files[0]['size'] * (count($files) - 1),
                    'files' => $files,
                    'inode_groups' => $inode_groups
                ];

                $duplicates[] = $duplicate_group;
            }
        }

        // 按潜在节省空间排序
        usort($duplicates, function($a, $b) {
            return $b['potential_savings'] - $a['potential_savings'];
        });

        return [
            'directory' => $directory,
            'total_files_scanned' => count($file_map),
            'duplicate_groups' => count($duplicates),
            'total_duplicate_copies' => array_sum(array_map(function($group) {
                return $group['total_copies'];
            }, $duplicates)),
            'potential_space_savings' => array_sum(array_map(function($group) {
                return $group['potential_savings'];
            }, $duplicates)),
            'duplicates' => $duplicates
        ];
    }

    public function createDeduplicationPlan($directory) {
        $result = $this->findDuplicateFiles($directory);

        if (isset($result['error'])) {
            return $result;
        }

        $plan = [];

        foreach ($result['duplicates'] as $group_index => $group) {
            if ($group['unique_inodes'] === 1) {
                // 所有文件共享同一个inode(已经是硬链接)
                continue;
            }

            // 选择保留的文件(最旧的文件)
            $oldest_file = null;
            foreach ($group['files'] as $file) {
                if ($oldest_file === null || $file['mtime'] < $oldest_file['mtime']) {
                    $oldest_file = $file;
                }
            }

            $group_plan = [
                'keep' => $oldest_file['path'],
                'replace' => [],
                'savings' => $group['potential_savings']
            ];

            foreach ($group['files'] as $file) {
                if ($file['path'] !== $oldest_file['path']) {
                    $group_plan['replace'][] = $file['path'];
                }
            }

            $plan[] = $group_plan;
        }

        return [
            'directory' => $directory,
            'plan_count' => count($plan),
            'total_savings' => array_sum(array_map(function($p) {
                return $p['savings'];
            }, $plan)),
            'plan' => $plan,
            'summary' => [
                'total_files' => $result['total_files_scanned'],
                'duplicate_groups' => $result['duplicate_groups'],
                'duplicate_copies' => $result['total_duplicate_copies']
            ]
        ];
    }
}

// 使用示例
$deduplicator = new FileDeduplicator();
$result = $deduplicator->findDuplicateFiles('/home/user/documents');

if (!isset($result['error'])) {
    echo "<h4>重复文件检测结果:</h4>";
    echo "扫描目录: {$result['directory']}<br>";
    echo "扫描文件数: " . number_format($result['total_files_scanned']) . "<br>";
    echo "重复文件组数: {$result['duplicate_groups']}<br>";
    echo "重复文件副本数: " . number_format($result['total_duplicate_copies']) . "<br>";
    echo "潜在节省空间: " . number_format($result['potential_space_savings']) . " 字节 (" .
         round($result['potential_space_savings'] / (1024 * 1024), 2) . " MB)<br>";

    if ($result['duplicate_groups'] > 0) {
        echo "<h5>重复文件组 (前3组):</h5>";
        for ($i = 0; $i < min(3, $result['duplicate_groups']); $i++) {
            $group = $result['duplicates'][$i];
            echo "<strong>组 #" . ($i + 1) . "</strong><br>";
            echo "文件大小: " . number_format($group['size']) . " 字节<br>";
            echo "副本数: {$group['total_copies']}<br>";
            echo "唯一inode数: {$group['unique_inodes']}<br>";
            echo "可节省空间: " . number_format($group['potential_savings']) . " 字节<br>";
            echo "文件列表 (前3个):<br>";
            for ($j = 0; $j < min(3, count($group['files'])); $j++) {
                $file = $group['files'][$j];
                echo "  - {$file['path']} (inode: {$file['inode']})<br>";
            }
            if (count($group['files']) > 3) {
                echo "  ... 还有 " . (count($group['files']) - 3) . " 个文件<br>";
            }
            echo "<br>";
        }

        // 生成去重计划
        $plan = $deduplicator->createDeduplicationPlan('/home/user/documents');
        if (!isset($plan['error'])) {
            echo "<div class='alert alert-info'>可创建 {$plan['plan_count']} 个硬链接,节省 " .
                 round($plan['total_savings'] / (1024 * 1024), 2) . " MB 空间</div>";
        }
    }
} else {
    echo "错误: " . $result['error'];
}
?>