编程语言cppC++17文件系统库和并行算法
星野暗涌📚 导读
📌 一、为什么要关心 C++17 的这两部分?
在 C++17 之前,标准库对 文件系统操作 和 并行计算 支持都比较薄弱:
C++17 引入了两块关键能力:
这两者结合,可以很方便地写出“既能遍历磁盘、又能吃满多核 CPU”的实用程序。
📁 二、std::filesystem:现代 C++ 的文件系统库
🔧 2.1 基本概念与命名空间
概念讲解
fs::path:表示路径(文件 / 目录)
fs::directory_entry:目录中的一个条目
fs::directory_iterator:遍历目录
fs::recursive_directory_iterator:递归遍历目录树
代码示例
1 2 3 4 5 6
| #include <filesystem> namespace fs = std::filesystem;
fs::path p = "logs"; p /= "2026"; p /= "app.log";
|
🧱 2.2 fs::path:优雅处理路径
概念讲解
fs::path 用来安全、可移植地表示路径,支持拆分、拼接和转换字符串。
常见需求:
获取父目录 / 文件名 / 后缀名
构造多级路径
与 std::string 互转
代码示例
1 2 3 4 5 6
| fs::path p{"./data/file.txt"};
auto parent = p.parent_path(); auto filename = p.filename(); auto stem = p.stem(); auto ext = p.extension();
|
1 2 3 4 5 6
| std::string s = p.string(); fs::path p = "logs"; p /= "2026"; p /= "app.log";
std::string s = p.string();
|
📂 2.3 文件与目录的基本操作
概念讲解
常见操作:
检查路径是否存在、是否为文件 / 目录
创建目录(含递归创建)
删除文件或整个目录树
拷贝、重命名、移动
代码示例
1 2 3 4 5 6 7 8 9
| fs::path p{"example.txt"};
if (fs::exists(p)) { if (fs::is_regular_file(p)) { } else if (fs::is_directory(p)) { } }
|
1 2 3 4 5 6 7 8 9 10
| // 创建目录 fs::create_directory("output");
if (fs::exists(p)) { if (fs::is_regular_file(p)) { // 普通文件 } else if (fs::is_directory(p)) { // 目录 } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| fs::path p{"example.txt"};
fs::create_directories("output/images/2026");
fs::create_directories("output/images/2026");
fs::remove("output/tmp.txt");
fs::remove_all("output/images");
|
1 2 3 4 5 6 7 8
| fs::create_directory("output");
fs::remove("output/tmp.txt");
fs::remove_all("output/images");
|
1 2 3 4 5 6
| fs::copy("a.txt", "b.txt", fs::copy_options::overwrite_existing);
fs::rename("old_name.txt", "new_name.txt");
|
1 2 3
| for (const auto &entry : fs::directory_iterator{"."}) { std::cout << entry.path() << "n"; }
|
🔍 2.4 遍历目录与递归遍历
概念讲解
使用 fs::directory_iterator 遍历当前目录
使用 fs::recursive_directory_iterator 递归遍历目录树
搭配条件判断筛选特定后缀(如 .txt / .log)
代码示例
1 2 3
| for (const auto &entry : fs::recursive_directory_iterator{"./data"}) { std::cout << entry.path() << "n"; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <filesystem> #include <iostream> #include <fstream>
namespace fs = std::filesystem;
void print_perms(fs::perms p) { using fs::perms; auto show = [=](char op, perms perm) { std::cout << ((p & perm) == perms::none ? '-' : op); };
show('r', perms::owner_read); show('w', perms::owner_write); show('x', perms::owner_exec); std::cout << ' '; show('r', perms::group_read); show('w', perms::group_write); show('x', perms::group_exec); std::cout << ' '; show('r', perms::others_read); show('w', perms::others_write); show('x', perms::others_exec); std::cout << "n"; }
int main() { fs::path p{"config/app.ini"}; std::ofstream(p).close();
fs::file_status st = fs::status(p); if (fs::is_regular_file(st)) { std::cout << p << " is a regular filen"; }
std::cout << "perms: "; print_perms(st.permissions());
fs::permissions(p, fs::perms::owner_write, fs::perm_options::add); fs::permissions(p, fs::perms::others_write, fs::perm_options::remove);
std::cout << "after change: "; print_perms(fs::status(p).permissions()); }
|
🛡️ 2.5 文件状态与权限:fs::status + fs::permissions
概念讲解
fs::is_regular_file(st)
fs::is_directory(st)
fs::is_symlink(st)
权限使用 fs::perms 枚举表示,例如:
owner_read / owner_write / owner_exec
group_read / group_write / group_exec
others_read / others_write / others_exec
修改权限使用 fs::permissions(path, perms, fs::perm_options::xxx)
replace:直接覆盖现有权限
add:在原有基础上增加
remove:在原有基础上移除
代码示例:查看类型 + 打印权限 + 修改权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <filesystem> #include <iostream>
namespace fs = std::filesystem;
int main() { fs::path root{"./data"}; std::uintmax_t total_size = 0;
for (const auto &entry : fs::recursive_directory_iterator{root}) { if (fs::is_regular_file(entry.path())) { total_size += fs::file_size(entry.path()); } }
namespace fs = std::filesystem;
int main() { fs::path root{"./data"}; std::uintmax_t total_size = 0;
for (const auto &entry : fs::recursive_directory_iterator{root}) { if (fs::is_regular_file(entry.path())) { total_size += fs::file_size(entry.path()); } }
std::cout << "Total size: " << total_size << " bytesn"; }
|
小贴士:Windows 下权限本质是 ACL,fs::perms 只是一个抽象映射;想做精细权限控制时,仍需要调用平台专用 API。
📏 2.6 示例:统计目录下所有文件总大小(顺序版本)
1 2 3 4 5 6 7 8 9 10 11
| #include <execution> #include <algorithm> #include <vector>
std::vector<int> v = {};
std::sort(std::execution::seq, v.begin(), v.end());
std::sort(std::execution::par, v.begin(), v.end());
|
这已经是很多工具脚本的雏形了,后面我们会用并行算法把这种任务“多核加速”。
⚡ 三、C++17 并行算法与 <execution>
🚦 3.1 执行策略(Execution Policy)
概念讲解
C++17 定义了三种主要执行策略:
std::execution::seq:顺序执行,行为与传统 <algorithm> 一致。
std::execution::par:允许并行执行,通常使用多个线程。
std::execution::par_unseq:并行 + 向量化(SIMD),要求算法对顺序不敏感且无数据竞争。
代码示例
1
| init op a[0] op a[1] op ... op a[n-1]
|
1 2 3 4 5 6 7 8 9 10 11
| #include <numeric> #include <execution> #include <vector>
std::vector<int> v = {1, 2, 3, 4, 5};
int s1 = std::reduce(std::execution::seq, v.begin(), v.end(), 0);
int s2 = std::reduce(std::execution::par, v.begin(), v.end(), 0);
|
📚 3.2 并行算法到底在干什么?(核心概念)
可以把并行算法 + 执行策略理解成:
把「一段区间上的同构操作」拆分成多块,交给多个线程同时处理,最后把各块结果合并。
输入仍然是一对迭代器 [first, last),比如 vector / array / 普通数组
算法本身还是熟悉的那几个:sort / for_each / transform / reduce / transform_reduce 等
区别只在于:
顺序策略:按迭代顺序,一个元素一个元素处理
- 并行策略:把区间切成若干子区间,在不同线程上分别处理,再用“归约操作”合并
reduce 是 C++17 新增的“可并行求和(或一般归约)”算法,语义上是:
但具体分块方式、合并顺序都不保证,所以要求 op 满足:
典型用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <numeric> #include <execution> #include <vector>
std::vector<int> v = {1, 2, 3, 4};
int sum_sq_seq = std::transform_reduce( std::execution::seq, v.begin(), v.end(), 0, std::plus<>{}, [](int x) { return x * x; } );
int sum_sq_par = std::transform_reduce( std::execution::par, v.begin(), v.end(), 0, std::plus<>{}, [](int x) { return x * x; } );
|
小结:当你只是要把一堆数字、结构体中的某个数值字段做「求和 / 求积 / min / max」这类操作时,reduce 是首选。
很多时候你希望:
先把元素变成某个标量(例如:长度、平方、大小)
再对这些标量做 reduce
传统写法通常要两步:
1 2 3 4 5 6 7 8 9
| std::uintmax_t total_size = std::transform_reduce( std::execution::par, files.begin(), files.end(), static_cast<std::uintmax_t>(0), std::plus<>{}, [](const fs::path& p) { return fs::file_size(p); } );
|
std::transform_reduce 把这两步合成一步,并且对 transform + reduce 整体做并行优化:
1 2 3 4 5 6 7 8 9
| #include <execution> #include <numeric> #include <vector>
std::vector<int> v = {};
int sum = std::reduce(std::execution::par, v.begin(), v.end(), 0);
|
在我们「统计目录树总大小」的示例中:
- 变换函数:
fs::path -> fs::file_size(path)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <filesystem> #include <execution> #include <numeric> #include <vector> #include <iostream>
namespace fs = std::filesystem;
int main() { fs::path root{"./data"}; std::vector<fs::path> files;
for (const auto &entry : fs::recursive_directory_iterator{root}) { if (fs::is_regular_file(entry.path())) { files.push_back(entry.path()); } }
std::uintmax_t total_size = std::transform_reduce( std::execution::par, files.begin(), files.end(), static_cast<std::uintmax_t>(0), std::plus<>{}, [](const fs::path &p) { return fs::file_size(p); } );
std::cout << "Total size: " << total_size << " bytesn"; }
|
一句话:
**transform_reduce**:对“经过一个映射后的值”做折叠,少一次中间容器,适合「路径 -> 文件大小」「结构体 -> 某个字段」这类场景。
📚 3.3 哪些算法支持并行执行策略?
大部分“元素遍历 + 计算”类算法都提供了执行策略重载,例如:
std::for_each
std::transform
std::sort / std::stable_sort
std::reduce(C++17 新增)
std::transform_reduce
std::count, std::count_if
std::find, std::find_if
std::inclusive_scan / std::exclusive_scan 等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <filesystem> #include <execution> #include <numeric> #include <vector> #include <iostream>
namespace fs = std::filesystem;
struct FileInfo { std::uintmax_t size; };
int main() { fs::path root{"./logs"}; std::vector<FileInfo> logs;
for (const auto &entry : fs::recursive_directory_iterator{root}) { if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".log") { logs.push_back(FileInfo{fs::file_size(entry.path())}); } }
std::uintmax_t total_size = std::transform_reduce( std::execution::par, logs.begin(), logs.end(), static_cast<std::uintmax_t>(0), std::plus<>{}, [](const FileInfo &info) { return info.size; } );
std::size_t count = logs.size(); double avg = count ? static_cast<double>(total_size) / count : 0.0;
std::cout << "Count: " << count << "n"; std::cout << "Average size: " << avg << " bytesn"; }
|
⚠️ 3.4 使用并行算法的注意事项
避免数据竞争:Lambda 中不要无保护地写同一共享变量。
不要依赖顺序:par / par_unseq 下执行顺序未指定,涉及严格顺序 IO 不适合使用。
数据量足够大再并行:小数据集上,并行开销可能超过收益。
🚀 四、std::filesystem + 并行算法:实战示例
📊 4.1 并行统计目录树中文件大小总和
思路:
用 std::filesystem 递归遍历目录,收集所有文件路径。
用 std::transform_reduce + std::execution::par 并行计算总大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <filesystem> #include <execution> #include <numeric> #include <vector> #include <iostream>
namespace fs = std::filesystem;
int main() { fs::path root{"./data"}; std::vector<fs::path> files;
for (const auto &entry : fs::recursive_directory_iterator{root}) { if (fs::is_regular_file(entry.path())) { files.push_back(entry.path()); } }
std::uintmax_t total_size = std::transform_reduce( std::execution::par, files.begin(), files.end(), static_cast<std::uintmax_t>(0), std::plus<>{}, [](const fs::path &p) { return fs::file_size(p); } );
std::cout << "Total size: " << total_size << " bytesn"; }
|
在这个例子中:
📈 4.2 并行统计某种扩展名文件的数量与平均大小
例如:统计目录下所有 .log 文件数量和平均大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <filesystem> #include <execution> #include <numeric> #include <vector> #include <iostream>
namespace fs = std::filesystem;
struct FileInfo { std::uintmax_t size; };
int main() { fs::path root{"./logs"}; std::vector<FileInfo> logs;
for (const auto &entry : fs::recursive_directory_iterator{root}) { if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".log") { logs.push_back(FileInfo{fs::file_size(entry.path())}); } }
std::uintmax_t total_size = std::transform_reduce( std::execution::par, logs.begin(), logs.end(), static_cast<std::uintmax_t>(0), std::plus<>{}, [](const FileInfo &info) { return info.size; } );
std::size_t count = logs.size(); double avg = count ? static_cast<double>(total_size) / count : 0.0;
std::cout << "Count: " << count << "n"; std::cout << "Average size: " << avg << " bytesn"; }
|
你可以轻松把这个逻辑扩展为更复杂的分析(例如区分不同子目录、过滤时间、结合正则匹配文件名等)。
📑 五、常用 std::filesystem / 并行算法函数速查表
| 函数 |
作用概述 |
| 路径与基本信息 |
|
fs::current_path() |
获取或设置当前工作目录 |
fs::path 构造 / /= |
表示路径;使用 /= 跨平台拼接子路径 |
p.parent_path() / p.filename() |
获取父目录 / 文件名部分 |
p.stem() / p.extension() |
获取不带扩展名的文件名 / 扩展名 |
fs::absolute(p) / fs::canonical(p) |
转为绝对路径;后者要求路径存在并做规范化 |
fs::relative(target, base) |
计算从 base 到 target 的相对路径 |
| 文件与目录操作 |
|
fs::exists(p) |
判断路径是否存在 |
fs::is_regular_file(p) / fs::is_directory(p) |
判断是否为普通文件 / 目录 |
fs::file_size(p) |
获取文件大小(字节) |
fs::last_write_time(p) |
获取 / 设置最后修改时间 |
fs::create_directory(p) / fs::create_directories(p) |
创建单层 / 多层目录 |
fs::copy_file(from, to, opts) / fs::copy(from, to, opts) |
拷贝单个文件 / 拷贝整个目录树(可递归 + 覆盖) |
fs::remove(p) / fs::remove_all(p) |
删除文件或空目录 / 递归删除目录树 |
| 状态与权限 |
|
fs::file_status / fs::status(p) |
获取文件状态(类型 + 权限) |
fs::perms |
权限枚举(owner/group/others 的 r/w/x) |
fs::permissions(p, perms, opts) |
按选项 replace/add/remove 设置或修改权限 |
fs::is_symlink(st) / fs::is_empty(p) |
判断是否为符号链接 / 文件或目录是否为空 |
fs::directory_options::skip_permission_denied |
在递归遍历时跳过权限不足的路径 |
| 目录遍历 |
|
fs::directory_iterator(dir) |
遍历目录的直接子项(非递归) |
fs::recursive_directory_iterator(dir) |
递归遍历目录树 |
begin(it) / end(it) |
与范围 for 等价的显式迭代器遍历方式 |
fs::directory_entry |
目录中的单个条目,提供 path()、is_directory() 等接口 |
| 并行算法与执行策略 |
|
std::execution::seq / par / par_unseq |
顺序 / 并行 / 并行 + SIMD 的执行策略 |
std::sort(policy, first, last) |
按给定策略排序(支持 seq / par / par_unseq) |
std::reduce(policy, first, last, init) |
对区间做归约(求和、求积、min/max 等),可并行 |
std::transform_reduce(policy, first, last, init, binary_op, unary_op) |
先用 unary_op 映射元素,再用 binary_op 做归约,常用于“映射 + 累加” |
std::for_each(policy, first, last, f) |
对区间内每个元素执行操作 f,可并行(注意避免数据竞争) |
std::count / std::count_if / std::find_if 等 |
按策略执行的计数 / 查找类算法,适合大数据集并行扫描 |
到这里,std::filesystem 的常见用法、权限/状态操作,以及与 C++17 并行算法(特别是 reduce / transform_reduce)的组合使用,就基本成体系了,可以直接拿去写工具脚本、日志分析器或者磁盘扫描类的小项目。
优先考虑使用 C++17 并行算法的场景:
任务可以抽象为“对一个范围内的元素做同构操作”。
不需要严格的处理顺序。
任务偏 CPU 密集(计算量较大)。
更适合自定义线程池 / 任务系统的场景:
需要复杂的任务依赖关系。
需要精细控制线程数量、绑定等。
涉及大量 IO、多种异步事件(网络、定时器等)。
如果要把这篇内容整理成博客,可以按下面的结构:
背景动机:C++17 解决哪些痛点(文件系统操作、并行计算)。
**std::filesystem** 基本用法:path、遍历、创建 / 删除、拷贝等。
并行算法与执行策略:std::execution 三种策略 + 常用算法。
综合示例:用 std::filesystem 收集数据,用并行算法处理。
实践注意事项:数据竞争、性能、平台支持等。