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编号,它是一个特殊的文件,包含指向目标文件的路径。
示例:
符号链接有自己的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. 使用
2. 使用文件ID(File ID)和卷序列号(Volume Serial Number)
3. 使用第三方扩展或Windows API
示例:
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'];
}
?>