5 - 文件系统支持库

C++标准库包含一个文件系统支持库,定义在<filesystem>中,并且位于std::filesystem名称空间中。它允许编写可移植的代码来处理文件系统,例如查询某路径是目录(Dir)还是文件(File),遍历路径内容,检索文件信息等操作。

文件系统支持库

路径(path)

路径可以是绝对的,也可以是相对的,并且可以包含文件名。下例定义了几个路径:

filesystem::path p1 {R"(D:\Foo\Bar)"};	// 这里使用原始字符串, 防止转义
filesystem::path p2 {"D:/Foo/Bar"};
filesystem::path p3 {R"(..\SomeFolder)"};

可以用.c_str(), .native(), 将路径插入流等形式输出这些路径:

cout << p1 << endl;
cout << p2 << endl;

// 在支持正反斜杠的Windows上的输出:
"D:\\Foo\\Bar"
"D:/Foo/Bar"

可以对路径进行一些修改操作:

  • 添加分隔符的修改:

    使用.append()operator/=可以将额外组件添加到路径中,并插入分隔符。

    filesystem::path p1 {R"(D:\Foo)"};
    p1.append("Bar1");
    p1 /= "Bar2";
    cout << p1 << endl;
    
    // 在支持正反斜杠的Windows上的输出:
    "D:\\Foo\\Bar1\\Bar2"
  • 不添加分隔符的修改:

    使用.concat()operator+=将字符串连接到现有路径,不会插入路径分隔符

    filesystem::path p1 {R"(D:\Foo)"};
    p1.concat("Bar1");
    p1 += "Bar2";
    cout << p1 << endl;
    
    // 在支持正反斜杠的Windows上的输出:
    "D:\\FooBar1Bar2"

如果想要遍历路径上的每个组件,可以用基于范围的for循环:

filesystem::path p1 {R"(D:\Foo\Bar)"};
for (const auto& component : p1)
{
    cout << component << endl;
}

// 在支持正反斜杠的Windows上的输出:
"D:"
"\\"
"Foo"
"Bar"

除此之外,路径还支持以下操作:

filesystem::path p1 {R"(D:\Foo\Bar\target.rar)"};
cout << p1.root_name() << endl;
cout << p1.filename() << endl;
cout << p1.stem() << endl;
cout << p1.extension() << endl;

// 在支持正反斜杠的Windows上的输出:
"D:"
"target.rar"
"target"
".rar"

更多操作详见path 类 | Microsoft Learn

目录条目(directory_entry)

有了路径后,我们得要访问它,因此需要构造一个directory_entry。它支持exists()is_directory()is_regular_file()file_size()last_write_time()等操作。

其中查询文件大小的示例如下:

filesystem::path path {"C:/windows/win.ini"};
filesystem::directory_entry dirEntry {path};
if (dirEntry.exists() && dirEntry.is_regular_file())
{
    cout << "File size: " << dirEntry.file_size() << endl;
}

// 输出:
File size: 92

更多操作详见directory_entry 类 | Microsoft Learn

辅助函数

<filesystem>头文件还定义了一些辅助函数,方便我们进行文件操作。例如,可以用copy()复制文件或目录;create_directory()在文件系统上创建一个新目录;exists()用来查询给定目录/文件是否存在;file_size()获取一个文件的大小;last_write_time()获取文件最后修改时间;space()用来查询文件系统上的可用空间等。

例如,下例输出了文件系统的容量和剩余空间的大小:

filesystem::space_info info {filesystem::space("C:")};
cout << "Capacity: " << info.capacity << endl;
cout << "Free: " << info.free << endl;

// 输出:
Capacity: 214748360704
Free: 28322189312

更多操作详见<filesystem> 函数 | Microsoft Learn

目录遍历

如果希望递归遍历给定目录中的所有文件和子目录,可以使用recursive_directory_iterator

using std::filesystem;
void printDirStructure(const path& p)
{
    if (!exists(p)) { return; }
    
    // 创建起始&结束递归迭代器
    recursive_directory_iterator begin {p};
    recursive_directory_iterator end {};
    for (auto iter {begin}; iter != end; ++iter)
    {
        // 为树状格式化准备的空格间距
        const string spacer(iter.depth() * 2,' ');
        // 需要解引用以访问directory_entry
        auto& entry {*iter};
        if (is_regular_file(entry))
        {
            cout << format("{}File: {} ({} bytes)", spacer,
                          entry.path().string(), file_size(entry)) << endl;
        } else if (is_directory(entry))
        {
            cout << format("{}Dir: {}", spacer, entry.path().string()) << endl;
        }
    }
}

也能使用directory_iterator迭代目录内容,但需要手动实现递归:

using std::filesystem;
void printDirSturcture(const path& p, size_t level = 0)
{
    if (!exists(p)) { return; }
    
    const string spacer(level * 2,' ');
    
    if (is_regular_file(p))
    {
        cout << format("{}File: {} ({} bytes)", spacer,
                       p.string(), file_size(p)) << endl;
    } else if (is_directory(entry))
    {
        cout << format("{}Dir: {}", spacer, p.string()) << endl;
        for (auto& entry : directory_iterator {p})
        {
            printDirStructure(entry, level + 1);
        }
    }
}

参考资料

  • 飘零的落花 - 现代C++详解
  • C++20高级编程