C++ STL之filesystem文件系统库详解
C++ STL之filesystem文件系统库详解
一、从 Boost 到标准库:filesystem 的前世今生
C++17 之前,跨平台文件操作是噩梦。Windows 用GetFileAttributesW、Linux 用stat、Mac 用NSFileManager——每套 API 各有千秋,但无一适用于跨平台场景。多数项目转向boost::filesystem,但 Boost 毕竟不是标准,团队引入需额外依赖、配置构建链。
C++17 正式将std::filesystem纳入标准库,头文件<filesystem>,命名空间std::filesystem。它统一了路径分隔符(/vs\)、文件属性提取、目录遍历、磁盘容量查询等操作,且无第三方依赖。
#include<filesystem>namespacefs=std::filesystem;intmain(){fs::path p="/home/user/file.txt";boolexists=fs::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 p="a/./b/../c/file.txt";fs::path norm=p.lexically_normal();// 结果: "a/c/file.txt"如果路径中有.表示当前目录,..表示上一级父目录,lexically_normal会将它们约简。注意这是纯字符串操作,不会访问磁盘。
make_preferred
将路径分隔符转为当前平台惯用格式:Windows 转\,POSIX 转/。
fs::path p="a/b/c";p.make_preferred();// Windows: a\b\c, Linux: a/b/c三、directory_entry 的缓存机制
directory_entry封装了文件系统的一条记录,包括路径和属性。它的核心设计是缓存:首次访问文件属性后,结果会被内部缓存,后续访问不再触发系统调用。
fs::directory_entryentry("/home/user/file.txt");autosize1=entry.file_size();// 触发 statautosize2=entry.file_size();// 返回缓存,无系统调用entry.refresh();// 强制刷新缓存autosize3=entry.file_size();// 重新 stat这在遍历大量文件时能显著减少 I/O:一次directory_iterator操作自动填充directory_entry,后续取file_size、last_write_time、file_type等都走缓存。
四、目录遍历:directory_iterator 与 recursive_directory_iterator
两套 API 对应两种遍历范围:
| 特性 | directory_iterator | recursive_directory_iterator |
|---|---|---|
| 遍历深度 | 仅当前目录 | 递归子目录 |
是否含... | 否 | 否 |
| 排序 | 未定义 | 未定义 |
| 跳过子目录 | 不适用 | disable_recursion_pending() |
voidlist_all(constfs::path&dir){fs::directory_iterator end;for(autoit=fs::directory_iterator(dir);it!=end;++it){// 只有第一层}for(autoit=fs::recursive_directory_iterator(dir);it!=fs::recursive_directory_iterator();++it){// 递归全部if(it->is_directory()&&skip(*it))it.disable_recursion_pending();}}两种迭代器返回的都是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 si=fs::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_options
fs::copy复制文件或目录,搭配copy_options控制行为:
| 选项 | 效果 |
|---|---|
none | 默认,已存在时报错 |
skip_existing | 跳过已有文件,不报错 |
overwrite_existing | 覆盖已有文件 |
update_existing | 仅目标更旧时才覆盖 |
recursive | 递归复制子目录 |
directories_only | 仅复制目录结构 |
create_symlinks | 复制为符号链接 |
voidbackup(constfs::path&src,constfs::path&dst){fs::copy_options opts=fs::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(autoit=fs::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 规则也是相对路径。
