C STL之filesystem文件系统库详解一、从 Boost 到标准库filesystem 的前世今生C17 之前跨平台文件操作是噩梦。Windows 用GetFileAttributesW、Linux 用stat、Mac 用NSFileManager——每套 API 各有千秋但无一适用于跨平台场景。多数项目转向boost::filesystem但 Boost 毕竟不是标准团队引入需额外依赖、配置构建链。C17 正式将std::filesystem纳入标准库头文件filesystem命名空间std::filesystem。它统一了路径分隔符/vs\、文件属性提取、目录遍历、磁盘容量查询等操作且无第三方依赖。#includefilesystemnamespacefsstd::filesystem;intmain(){fs::path p/home/user/file.txt;boolexistsfs::exists(p);return0;}二、path 类与正规化path是 filesystem 库的核心类型负责跨平台路径表示。它不检查路径是否真实存在只做字符串操作。常见操作操作说明p.root_name()返回根名Windows 的C:p.root_directory()返回根目录/或\p.parent_path()父路径p.filename()文件名含后缀p.stem()不带后缀的文件名p.extension()后缀含.正规化lexically_normal移除路径中的.、..和多余分隔符返回逻辑上干净的路径。fs::path pa/./b/../c/file.txt;fs::path normp.lexically_normal();// 结果: a/c/file.txt如果路径中有.表示当前目录..表示上一级父目录lexically_normal会将它们约简。注意这是纯字符串操作不会访问磁盘。make_preferred将路径分隔符转为当前平台惯用格式Windows 转\POSIX 转/。fs::path pa/b/c;p.make_preferred();// Windows: a\b\c, Linux: a/b/c原始路径: a/./b/../c/file.txtlexically_normal去除 .回退 ..正规化结果: a/c/file.txtmake_preferredWindows: a\\b\\cPOSIX: a/b/c三、directory_entry 的缓存机制directory_entry封装了文件系统的一条记录包括路径和属性。它的核心设计是缓存首次访问文件属性后结果会被内部缓存后续访问不再触发系统调用。fs::directory_entryentry(/home/user/file.txt);autosize1entry.file_size();// 触发 statautosize2entry.file_size();// 返回缓存无系统调用entry.refresh();// 强制刷新缓存autosize3entry.file_size();// 重新 stat这在遍历大量文件时能显著减少 I/O一次directory_iterator操作自动填充directory_entry后续取file_size、last_write_time、file_type等都走缓存。四、目录遍历directory_iterator 与 recursive_directory_iterator两套 API 对应两种遍历范围特性directory_iteratorrecursive_directory_iterator遍历深度仅当前目录递归子目录是否含...否否排序未定义未定义跳过子目录不适用disable_recursion_pending()voidlist_all(constfs::pathdir){fs::directory_iterator end;for(autoitfs::directory_iterator(dir);it!end;it){// 只有第一层}for(autoitfs::recursive_directory_iterator(dir);it!fs::recursive_directory_iterator();it){// 递归全部if(it-is_directory()skip(*it))it.disable_recursion_pending();}}是否是是否否开始遍历选择迭代器类型directory_iterator?recursive_directory_iterator?打开当前目录读取下一个条目还有条目?返回 directory_entry结束打开当前目录读取下一个条目还有条目?是目录且未禁用递归?递归进入子目录返回 directory_entry两种迭代器返回的都是directory_entry可直接调用is_regular_file()、file_size()、path()等。五、space_info磁盘容量查询std::filesystem::space()返回space_info结构体structspace_info{uintmax_t capacity;// 总容量uintmax_t free;// 剩余容量给调用者的配额uintmax_t available;// 可用容量含非特权用户的配额限制};fs::space_info sifs::space(/);std::cout总容量: si.capacity/1e9 GB\n;std::cout剩余: si.free/1e9 GB\n;std::cout可用: si.available/1e9 GB\n;free与available的区别POSIX 系统上free是文件系统层未使用的块数available还要扣除 root 预留块。非 root 用户看到available小于free。六、copy 与 copy_optionsfs::copy复制文件或目录搭配copy_options控制行为选项效果none默认已存在时报错skip_existing跳过已有文件不报错overwrite_existing覆盖已有文件update_existing仅目标更旧时才覆盖recursive递归复制子目录directories_only仅复制目录结构create_symlinks复制为符号链接voidbackup(constfs::pathsrc,constfs::pathdst){fs::copy_options optsfs::copy_options::recursive|fs::copy_options::overwrite_existing|fs::copy_options::update_existing;fs::copy(src,dst,opts);}注意fs::copy在目录上默认不递归必须显式加copy_options::recursive才能复制子目录。七、面试题精选1.lexically_normal会访问磁盘吗不会。它是纯字符串操作只处理./和../路径分量不检查文件是否存在。如果需要解析符号链接或相对路径的绝对化用canonical或weakly_canonical会访问磁盘。2.directory_iterator返回的条目有顺序保证吗没有。遍历顺序由底层文件系统决定不可预测。若需有序遍历须手动收集到std::vector再排序。3.directory_entry缓存什么时间生效如何刷新首次调用file_size()、last_write_time()、status()等触发系统调用后缓存结果。文件在外部被修改时缓存变脏需调用refresh()刷新。缓存不自动感知外部变更。4.copy_options::recursive和copy_options::directories_only可以同时使用吗可以。组合后会在目标递归创建完整的目录结构但不复制任何文件。常用于备份前建好骨架。5.space_info::free和space_info::available实际有什么区别free是文件系统层空闲块available还要排除特权预留ext4 默认 5% 给 root。用户态程序应优先读available否则写的字节可能被ENOSPC拒绝。6.path的operator/做了什么与字符串拼接有何不同operator/会自动插入平台分隔符/或\且不会重复。字符串拼接可能导致a//b或a\b混用。优先用operator/而不是fs::path(a.string() / b.string())。7.recursive_directory_iterator如何跳过某个子目录调用disable_recursion_pending()析构后迭代器会跳过当前条目须是目录下的所有内容。for(autoitfs::recursive_directory_iterator(.);it!fs::recursive_directory_iterator();it){if(it-path().filename().git)it.disable_recursion_pending();elseprocess(*it);}8. 如何判断一个path是绝对路径p.is_absolute()检查是否绝对路径POSIX 以/开头Windows 含根名根目录p.is_relative()上述取反Windows 上C:/foo是绝对路径.是相对路径/foo仅含根目录但无根名按 Windows 规则也是相对路径。