C++17文件系统库和并行算法

C++17文件系统库和并行算法

 

现在让我们设好番茄钟放一首好听的音乐开始学习吧 🌈 😋


 

📚 导读

  • 了解为什么要关注 C++17 的 std::filesystem 和并行算法
  • 快速掌握常用 API 与并行执行策略
  • 通过实战示例感受“文件系统 + 并行”的威力

📌 一、为什么要关心 C++17 的这两部分?

在 C++17 之前,标准库对 文件系统操作并行计算 支持都比较薄弱:

  • 操作文件和目录通常依赖平台 API(如 POSIX、Win32)或 Boost.Filesystem。
  • 想利用多核 CPU,往往要手写线程池或使用第三方库。

C++17 引入了两块关键能力:

  • std::filesystem:跨平台的标准文件系统库。
  • std::execution 并行算法:在熟悉的 <algorithm> 基础上,通过执行策略轻松启用并行。

这两者结合,可以很方便地写出“既能遍历磁盘、又能吃满多核 CPU”的实用程序。


📁 二、std::filesystem:现代 C++ 的文件系统库

🔧 2.1 基本概念与命名空间

概念讲解

  • 头文件:<filesystem>
  • 命名空间:std::filesystem(通常起个别名 fs
  • 常用类型:
    • fs::path:表示路径(文件 / 目录)
    • fs::directory_entry:目录中的一个条目
    • fs::directory_iterator:遍历目录
    • fs::recursive_directory_iterator:递归遍历目录树

代码示例

#include <filesystem>
namespace fs = std::filesystem;

fs::path p = "logs";
p /= "2026";
p /= "app.log";

 

 


🧱 2.2 fs::path:优雅处理路径

概念讲解

fs::path 用来安全、可移植地表示路径,支持拆分、拼接和转换字符串。

常见需求:

  • 获取父目录 / 文件名 / 后缀名
  • 构造多级路径
  • std::string 互转

代码示例

fs::path p{"./data/file.txt"};

auto parent   = p.parent_path();   // ./data
auto filename = p.filename();      // file.txt
auto stem     = p.stem();          // file
auto ext      = p.extension();     // .txt

 

std::string s = p.string();
		fs::path p = "logs";
		p /= "2026";
		p /= "app.log";

		std::string s = p.string();

 


📂 2.3 文件与目录的基本操作

概念讲解

常见操作:

  • 检查路径是否存在、是否为文件 / 目录
  • 创建目录(含递归创建)
  • 删除文件或整个目录树
  • 拷贝、重命名、移动

代码示例

fs::path p{"example.txt"};

if (fs::exists(p)) {
    if (fs::is_regular_file(p)) {
        // 普通文件
    } else if (fs::is_directory(p)) {
        // 目录
    }
}

 

	// 创建目录
	fs::create_directory("output");

if (fs::exists(p)) {
    if (fs::is_regular_file(p)) {
        // 普通文件
    } else if (fs::is_directory(p)) {
        // 目录
    }
}

 

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");

 

// 创建目录
fs::create_directory("output");

	// 删除单个文件或空目录
	fs::remove("output/tmp.txt");

	// 递归删除目录树
	fs::remove_all("output/images");

 

// 拷贝文件(若存在则覆盖)
fs::copy("a.txt", "b.txt",
         fs::copy_options::overwrite_existing);

// 重命名 / 移动
fs::rename("old_name.txt", "new_name.txt");

 


🔍 2.4 遍历目录与递归遍历

概念讲解

  • 使用 fs::directory_iterator 遍历当前目录
  • 使用 fs::recursive_directory_iterator 递归遍历目录树
  • 搭配条件判断筛选特定后缀(如 .txt / .log

代码示例

for (const auto &entry : fs::directory_iterator{"."}) {
    std::cout << entry.path() << "n";
}

 

for (const auto &entry : fs::recursive_directory_iterator{"./data"}) {
    std::cout << entry.path() << "n";
}

 


🛡️ 2.5 文件状态与权限:fs::status + fs::permissions

概念讲解

  • fs::file_status:保存文件的“类型 + 权限”等元信息
  • fs::status(path):访问实际文件系统,返回 file_status
  • 常见文件类型判断:
    • 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:在原有基础上移除

代码示例:查看类型 + 打印权限 + 修改权限

#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();      // 确保文件存在

    // 1. 获取状态并判断类型
    fs::file_status st = fs::status(p);
    if (fs::is_regular_file(st)) {
        std::cout << p << " is a regular filen";
    }

    // 2. 打印当前权限
    std::cout << "perms: ";
    print_perms(st.permissions());

    // 3. 为 owner 添加写权限,为 others 移除写权限
    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());
}

 

小贴士:Windows 下权限本质是 ACL,fs::perms 只是一个抽象映射;想做精细权限控制时,仍需要调用平台专用 API。


📏 2.6 示例:统计目录下所有文件总大小(顺序版本)

#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";
	}

 

#include <filesystem>
#include <iostream>

    std::cout << "Total size: " << total_size << " bytesn";
}

 

这已经是很多工具脚本的雏形了,后面我们会用并行算法把这种任务“多核加速”。


⚡ 三、C++17 并行算法与 <execution>

🚦 3.1 执行策略(Execution Policy)

概念讲解

C++17 定义了三种主要执行策略:

  • std::execution::seq:顺序执行,行为与传统 <algorithm> 一致。
  • std::execution::par:允许并行执行,通常使用多个线程。
  • std::execution::par_unseq:并行 + 向量化(SIMD),要求算法对顺序不敏感且无数据竞争。

代码示例

		#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());

 

#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());

 


📚 3.2 并行算法到底在干什么?(核心概念)

可以把并行算法 + 执行策略理解成:

把「一段区间上的同构操作」拆分成多块,交给多个线程同时处理,最后把各块结果合并。

  • 输入仍然是一对迭代器 [first, last),比如 vector / array / 普通数组
  • 算法本身还是熟悉的那几个:sort / for_each / transform / reduce / transform_reduce
  • 区别只在于:
    • 顺序策略:按迭代顺序,一个元素一个元素处理
    • 并行策略:把区间切成若干子区间,在不同线程上分别处理,再用“归约操作”合并

reduce 是 C++17 新增的“可并行求和(或一般归约)”算法,语义上是:

init op a[0] op a[1] op ... op a[n-1]

 

但具体分块方式、合并顺序都不保证,所以要求 op 满足:

  • 交换律:a op b == b op a
  • 结合律:(a op b) op c == a op (b op c)

典型用法:

#include <numeric>
#include <execution>
#include <vector>

std::vector<int> v = {1, 2, 3, 4, 5};

// 顺序 reduce(类似 accumulate,但可由库自行分块)
int s1 = std::reduce(std::execution::seq, v.begin(), v.end(), 0);

// 并行 reduce:在多线程上累加
int s2 = std::reduce(std::execution::par, v.begin(), v.end(), 0);

 

小结:当你只是要把一堆数字、结构体中的某个数值字段做「求和 / 求积 / min / max」这类操作时,reduce 是首选。

很多时候你希望:

  1. 先把元素变成某个标量(例如:长度平方大小
  2. 再对这些标量做 reduce

传统写法通常要两步:

// 1. transform 到一个中间容器
// 2. 再对中间容器做 accumulate / reduce

 

std::transform_reduce 把这两步合成一步,并且对 transform + reduce 整体做并行优化

#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; }
);

 

在我们「统计目录树总大小」的示例中:

  • 变换函数:fs::path -> fs::file_size(path)
  • 归约函数:std::plus<>(把大小相加)
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);
    }
);

 

一句话:

<strong>reduce</strong>:对“原始元素”做折叠

<strong>transform_reduce</strong>:对“经过一个映射后的值”做折叠,少一次中间容器,适合「路径 -> 文件大小」「结构体 -> 某个字段」这类场景。


📚 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
#include <execution>
#include <numeric>   // std::reduce
#include <vector>

std::vector<int> v = {/* ... */};

// 并行求和
int sum = std::reduce(std::execution::par,
                      v.begin(), v.end(), 0);

 


⚠️ 3.4 使用并行算法的注意事项

  • 避免数据竞争:Lambda 中不要无保护地写同一共享变量。
  • 不要依赖顺序par / par_unseq 下执行顺序未指定,涉及严格顺序 IO 不适合使用。
  • 数据量足够大再并行:小数据集上,并行开销可能超过收益。

🚀 四、std::filesystem + 并行算法:实战示例

📊 4.1 并行统计目录树中文件大小总和

思路:

  1. std::filesystem 递归遍历目录,收集所有文件路径。
  2. std::transform_reduce + std::execution::par 并行计算总大小。
#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;

    // 1. 收集所有文件路径
    for (const auto &entry : fs::recursive_directory_iterator{root}) {
        if (fs::is_regular_file(entry.path())) {
            files.push_back(entry.path());
        }
    }

    // 2. 并行计算总大小
    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";
}

 

在这个例子中:

  • std::filesystem 负责与 OS 打交道。
  • std::transform_reduce 负责并行把“路径 → 大小”的映射和值累加起来。

📈 4.2 并行统计某种扩展名文件的数量与平均大小

例如:统计目录下所有 .log 文件数量和平均大小。

#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、多种异步事件(网络、定时器等)。

如果要把这篇内容整理成博客,可以按下面的结构:

  1. 背景动机:C++17 解决哪些痛点(文件系统操作、并行计算)。
  2. <strong>std::filesystem</strong> 基本用法:path、遍历、创建 / 删除、拷贝等。
  3. 并行算法与执行策略std::execution 三种策略 + 常用算法。
  4. 综合示例:用 std::filesystem 收集数据,用并行算法处理。
  5. 实践注意事项:数据竞争、性能、平台支持等。