22、filesystem库(C++17):路径操作、目录遍历、文件信息查询、实战:磁盘空间分析工具
说实话,C++程序员等一个标准化的文件系统库,等了太久了。在C++17之前,你要操作文件路径、遍历目录,要么用Boost.Filesystem,要么自己写平台相关的API——Windows上用GetFileAttributes,Linux上撸stat,代码写得又臭又长。我个人习惯是能不用平台API就不用,移植性太差了。
C++17引入的std::filesystem,说白了就是Boost.Filesystem的标准化版本。它把路径操作、目录遍历、文件信息查询这些日常高频需求,全部封装成了优雅的C++接口。今天我们就把它掰开揉碎,最后用这些知识写一个磁盘空间分析工具。
22.1 路径操作:别再用字符串拼接了
很多新手喜欢用字符串拼接来构造路径,比如string path = "/data/" + filename + ".txt";。嗯,这里要注意:不同操作系统的路径分隔符不一样,Windows用反斜杠,Linux用正斜杠。你手动拼接,换平台就崩。
std::filesystem::path就是来解决这个问题的。它自动处理分隔符,还提供了一系列成员函数来操作路径的各个部分。
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path p = "/home/user/docs/report.txt";
std::cout << "根路径: " << p.root_path() << "\n"; // "/"
std::cout << "父路径: " << p.parent_path() << "\n"; // "/home/user/docs"
std::cout << "文件名: " << p.filename() << "\n"; // "report.txt"
std::cout << "主干名: " << p.stem() << "\n"; // "report"
std::cout << "扩展名: " << p.extension() << "\n"; // ".txt"
// 拼接路径,自动处理分隔符
fs::path newPath = p.parent_path() / "backup" / "report_2024.txt";
std::cout << "新路径: " << newPath << "\n";
// 输出: "/home/user/docs/backup/report_2024.txt"
return 0;
}
我个人习惯用operator/=或者operator/来拼接路径,而不是用string的+。你想想看,/操作符在路径语境下就是"追加子路径",语义非常清晰。
p.string()。Windows下想获取原生格式(反斜杠),用p.native()。
22.2 目录遍历:递归与非递归
遍历目录是文件系统操作里最常用的功能之一。比如你要统计一个项目里有多少个.cpp文件,或者要找出所有超过100MB的日志文件。C++17提供了两种迭代器:directory_iterator(非递归)和recursive_directory_iterator(递归)。
我在项目中遇到过一个问题:用recursive_directory_iterator遍历一个大目录树,结果遇到了符号链接循环,程序直接崩溃了。后来我加上了skip_permission_denied选项,才稳定下来。
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
void list_files(const fs::path& dir) {
// 非递归:只遍历当前目录
std::cout << "=== 非递归遍历: " << dir << " ===\n";
for (const auto& entry : fs::directory_iterator(dir)) {
std::cout << (entry.is_directory() ? "[DIR] " : "[FILE] ")
<< entry.path().filename() << "\n";
}
// 递归:遍历所有子目录
std::cout << "=== 递归遍历: " << dir << " ===\n";
for (const auto& entry : fs::recursive_directory_iterator(
dir,
fs::directory_options::skip_permission_denied)) {
std::cout << std::string(entry.depth(), ' ') // 缩进显示层级
<< (entry.is_directory() ? "[DIR] " : "[FILE] ")
<< entry.path().filename() << "\n";
}
}
filesystem_error异常。建议始终使用directory_options::skip_permission_denied,或者在外层捕获异常。
22.3 文件信息查询:不只是大小和修改时间
std::filesystem提供了file_status和一系列自由函数来查询文件信息。除了常见的文件大小、修改时间,还能查文件类型、权限、硬链接数等。
| 函数 | 返回值 | 说明 |
|---|---|---|
file_size(p) |
uintmax_t |
文件大小(字节) |
last_write_time(p) |
file_time_type |
最后修改时间 |
status(p) |
file_status |
文件类型和权限 |
is_directory(p) |
bool |
是否是目录 |
is_regular_file(p) |
bool |
是否是普通文件 |
hard_link_count(p) |
uintmax_t |
硬链接数 |
void print_file_info(const fs::path& p) {
try {
auto ft = fs::last_write_time(p);
auto ftime = decltype(ft)::clock::to_time_t(ft);
std::cout << "文件: " << p.filename() << "\n"
<< " 大小: " << fs::file_size(p) << " 字节\n"
<< " 修改时间: " << std::ctime(&ftime)
<< " 类型: " << (fs::is_directory(p) ? "目录" : "文件") << "\n";
} catch (const fs::filesystem_error& e) {
std::cerr << "查询失败: " << e.what() << "\n";
}
}
22.4 实战:磁盘空间分析工具
好了,理论说完了,我们来写点真正有用的东西。磁盘空间分析工具——就是那种扫描目录,告诉你哪个文件夹占空间最大的工具。Windows上的TreeSize、Linux上的du命令,原理都差不多。
核心逻辑很简单:递归遍历目录,累加每个文件的大小,按目录分组统计。我写这个工具时,发现一个坑:file_size对目录调用会抛出异常,所以必须先判断是不是普通文件。
#include <filesystem>
#include <iostream>
#include <map>
#include <iomanip>
namespace fs = std::filesystem;
struct DirInfo {
uintmax_t total_size = 0;
uintmax_t file_count = 0;
uintmax_t dir_count = 0;
};
// 递归扫描目录,返回该目录的总大小和信息
DirInfo scan_directory(const fs::path& dir,
std::map<fs::path, DirInfo>& result,
int depth = 0) {
DirInfo info;
try {
for (const auto& entry : fs::recursive_directory_iterator(
dir, fs::directory_options::skip_permission_denied)) {
if (fs::is_regular_file(entry.path())) {
uintmax_t size = fs::file_size(entry.path());
info.total_size += size;
info.file_count++;
} else if (fs::is_directory(entry.path())) {
info.dir_count++;
}
}
} catch (const std::exception& e) {
std::cerr << "扫描 " << dir << " 时出错: " << e.what() << "\n";
}
result[dir] = info;
return info;
}
// 格式化显示大小
std::string format_size(uintmax_t bytes) {
const char* units[] = {"B", "KB", "MB", "GB", "TB"};
int unit_idx = 0;
double size = static_cast<double>(bytes);
while (size >= 1024.0 && unit_idx < 4) {
size /= 1024.0;
unit_idx++;
}
std::ostringstream oss;
oss << std::fixed << std::setprecision(2) << size << " " << units[unit_idx];
return oss.str();
}
int main(int argc, char* argv[]) {
fs::path root = (argc > 1) ? argv[1] : fs::current_path();
std::cout << "正在扫描: " << root << "\n";
std::cout << "请稍候...\n\n";
std::map<fs::path, DirInfo> result;
DirInfo total = scan_directory(root, result);
std::cout << "扫描完成!\n";
std::cout << "总大小: " << format_size(total.total_size) << "\n";
std::cout << "文件数: " << total.file_count << "\n";
std::cout << "目录数: " << total.dir_count << "\n\n";
// 按大小排序输出前10个目录
std::cout << "占用空间最大的目录:\n";
std::cout << std::left << std::setw(60) << "目录"
<< std::right << std::setw(15) << "大小"
<< std::setw(10) << "文件数" << "\n";
std::cout << std::string(85, '-') << "\n";
// 排序(这里简单起见,只输出根目录的信息)
// 实际项目可以用vector排序
for (const auto& [path, info] : result) {
std::cout << std::left << std::setw(60) << path.string()
<< std::right << std::setw(15) << format_size(info.total_size)
<< std::setw(10) << info.file_count << "\n";
}
return 0;
}
- 用
recursive_directory_iterator递归遍历,加上skip_permission_denied避免权限错误 - 先判断
is_regular_file再调用file_size,否则会抛异常 - 用
map按目录路径存储统计结果,方便后续排序和展示 - 格式化显示时,把字节数转成KB/MB/GB,人类可读
这个工具虽然简单,但已经能解决80%的磁盘空间分析需求了。你想想看,把它稍微扩展一下——加上排序、过滤、导出CSV——就是一个完整的实用工具。我在公司内部就用类似的脚本定期扫描服务器磁盘,自动发告警邮件。
以上就是filesystem库的核心内容。路径操作、目录遍历、文件信息查询,这三个技能组合起来,能解决大量日常文件处理需求。磁盘空间分析工具只是其中一个应用场景,你完全可以举一反三,写出文件同步工具、日志清理器、备份校验程序等等。