使用标准的C++/C++11,14,17/C语言,检查文件是否存在的最快方法是什么?

630

我想找到在标准C++11、14、17或C中检查文件是否存在的最快方法。我有数千个文件,在对它们进行任何操作之前,我需要检查它们是否都存在。在下面的函数中,可以用什么代替/* SOMETHING */

inline bool exist(const std::string& name)
{
    /* SOMETHING */
}

26
你如何知道在进行exists()检查和“对其执行某些操作”之间文件未被删除?这是一个TOCTOU问题。 - pilcrow
10
不错的观点,但有很多应用程序并不需要那么高的正确性要求。例如,git push 命令可能不会在初始脏检查后再次确认您是否触及了工作树。 - millimoose
13
我想不出一个不需要它的C/C++实现。——Windows没有提供POSIX环境。 - Jim Balter
3
可能是“std :: ofstream,在写入之前检查文件是否存在”的重复问题。 - MD XF
3
为什么要这样做?比如说做些什么?如果需要打开文件,就尝试打开它并处理不存在的错误。在此之前添加另一个检查只是浪费时间和空间,并且会增加时间窗口问题。 - user207421
显示剩余8条评论
23个回答

7

您还可以执行bool b = std::ifstream('filename').good();。没有分支指令(如if),它必须更快地执行,因为需要调用数千次。


正如被接受的答案所示,这是不正确的。任何严肃的编译器都可能会发出相同的代码,无论您是否放入if。与普通C变体相比,构建ifstream对象(即使在堆栈上)会产生额外的开销。 - minexew

6
如果您需要区分文件和目录,请考虑以下两者均使用 stat 的方法,PherricOxide 已证明其是最快的标准工具:
#include <sys/stat.h>
int FileExists(char *path)
{
    struct stat fileStat; 
    if ( stat(path, &fileStat) )
    {
        return 0;
    }
    if ( !S_ISREG(fileStat.st_mode) )
    {
        return 0;
    }
    return 1;
}

int DirExists(char *path)
{
    struct stat fileStat;
    if ( stat(path, &fileStat) )
    {
        return 0;
    }
    if ( !S_ISDIR(fileStat.st_mode) )
    {
        return 0;
    }
    return 1;
}

5
您可以使用std::ifstream,像is_openfail这样的函数,例如以下代码(cout中的"open"表示文件是否存在):

输入图像描述

输入图像描述

摘自此答案

4
所有其他答案都集中在逐个检查每个文件,但如果文件都在同一个目录(文件夹)中,只需读取该目录并检查每个文件名的存在可能更有效。即使文件分布在多个目录中,这种方法可能更有效,具体取决于目录与文件的确切比率。一旦开始接近每个目标文件都在自己的目录中,或者在同一目录中有很多其他不想检查的文件时,我预计它最终会变得不如逐个检查每个文件的方法高效。一个好的启发式:处理已经拥有的一堆数据比向操作系统请求任何数量的数据要快得多。相对于单个机器指令,系统调用开销非常大。因此,从操作系统请求“给我此目录中所有文件的完整列表”,然后遍历该列表几乎总是更快的,而从操作系统请求“给我有关此文件的信息”,“好的,现在给我有关另一个文件的信息”,“现在给我关于……的信息”,等等则更慢。
每个优秀的C库都会以高效的方式实现其“遍历目录中的所有文件”API,就像缓冲I/O一样 - 在内部,它会一次性从操作系统中读取一个大的目录条目列表,即使这些API看起来像是逐个请求每个条目。
如果我有这样的需求,我会:
  1. 尽一切可能鼓励设计和使用,使得所有文件都在一个文件夹中,并且没有其他文件在该文件夹中。
  2. 将所需文件名的列表放入内存中的数据结构中,具有O(1)或至少O(log(n))的查找和删除时间(如哈希表或二叉树)。
  3. 列出该目录中的文件,并在从内存中的“列表”(哈希表或二叉树)中进行“勾选”(删除)每个文件。
除了具体使用情况以外,也许我会选择保留哈希映射或树中的条目,而是为每个条目跟踪一个“我是否拥有此文件?”布尔值,并找出一个数据结构,使得询问“我是否拥有每个文件?”的复杂度为O(1)。也许可以使用二叉树,但非叶节点的结构还包括其叶节点布尔值的逻辑与。这样可以很好地扩展——在设置叶节点的布尔值后,您只需沿着树向上移动并将每个节点的“是否拥有此文件?”布尔值设置为其子节点布尔值的&& (如果您每次尝试将其中一个叶节点设置为true时都始终按照此过程进行,则不需要递归下降到其他子节点,因为当且仅当它们的所有子节点都为真时,它们才为真)。
很遗憾,在C++17之前,没有标准的方法可以做到这一点。
C++17有一个std::filesystem::directory_iterator
当然,还有一个对应的boost::filesystem::directory_iterator,我假设它可以在旧版本的C++中工作。
最接近标准C的方法是使用dirent.h中的opendirreaddir。那是一个标准的C接口,只不过是在POSIX中标准化而不是在C标准本身中。它可以在Mac OS、Linux、所有BSD、其他UNIX/UNIX-like系统以及任何其他POSIX/SUS系统上直接使用。对于Windows,有一个dirent.h实现,你只需要下载并将其放入你的include路径即可。
然而,由于你正在寻找最快的方法,你可能需要超越可移植/标准化的东西。
在Linux上,您可能可以通过手动使用原始系统调用getdents64来指定缓冲区大小以优化性能。
在Windows上,经过一番挖掘,看起来为了获得最佳性能,您需要在可能的情况下使用FindFirstFileExFindExInfoBasicFIND_FIRST_EX_LARGE_FETCH,但很多开源库(如上述的dirent.h)似乎并不这样做。但是对于需要处理比最近几个Windows版本旧的内容的代码,您可以只使用简单的FindFirstFile而不使用额外的标志。
Plan 9不会被上述任何内容覆盖,在那里您将需要使用dirreaddirreadall(如果您可以安全地假设您拥有足够的内存来容纳整个目录内容)。如果您想要更多关于性能缓冲区大小的控制,请使用普通的readread并解码目录条目数据-它们采用文档化的机器无关格式,并且我认为提供了辅助函数。

我不知道其他操作系统的情况。


我可能会稍后编辑这个答案并进行一些测试。其他人也可以编辑测试结果。


3
all_of (begin(R), end(R), [](auto&p){ exists(p); })

其中R是您的路径序列,exists()来自未来的std或当前的boost。如果您自己创建,请保持简单。

bool exists (string const& p) { return ifstream{p}; }

分支解决方案并不是完全糟糕的,它也不会占用文件描述符。
bool exists (const char* p) {
    #if defined(_WIN32) || defined(_WIN64)
    return p && 0 != PathFileExists (p);
    #else
    struct stat sb;
    return p && 0 == stat (p, &sb);
    #endif
}

PathFileExists 函数仅支持 MAX_PATH (260) 字符的路径长度;而 GetFileAttributes 函数则没有此限制。 - Felix Dombek
GetFileAttributes 也是受 MAX_PATH 限制的。文档描述了一种解决方法——如果你使用绝对路径、Unicode,并在路径名前添加一个特殊前缀字符串。我认为我们已经偏离了与 Windows 相关的响应。 - John
1
GetFileAttributesW 没有限制。 - Laurie Stearn

3

在C++17中:

#include <experimental/filesystem>

bool is_file_exist(std::string& str) {   
    namespace fs = std::experimental::filesystem;
    fs::path p(str);
    return fs::exists(p);
}

8
在C++17中,文件系统不再是实验性的。 - Quest

3

其实有更简单的方法。

#include <fstream>
#include <iostream>

void FileExists(std::string myfile){
std::ifstream file(myfile.c_str());

if (file) {
    std::cout << "file exists" << std::endl;
}
else {
    std::cout << "file doesn't exist" << std::endl;
}
}

int main() {
FileExists("myfile.txt");

return 0;
}

1
如果文件存在但用户没有读取权限,这个功能无法正常工作。此外,它已经被另一个答案涵盖了。 - Sneftel

1
这是一个简单的例子!
#include <iostream>
#include <fstream>
using namespace std;
    
void main(){
   SearchFile("test.txt");
}

bool SearchFile(const char *file)
{
   ifstream infile(file);
   if (!infile.good())
   {
    // If file is not there
    exit(1);
   }
}

0

检查文件是否存在并且是否有权限读取的最快方法就是使用 C 语言,这种方式更快速并且可以在任何版本的 C++ 中使用。

解决方案:在 C 中,有一个名为 errno.h 的库,其中包含一个外部(全局)整数变量称为 errno,其中包含可用于识别错误类型的数字。

    #include <stdio.h>
    #include <stdbool.h>
    #include <errno.h>

    bool isFileExist(char fileName[]) {
        FILE *fp = fopen(fileName, "r");
        if (fp) {
            fclose(fp);
            return true;
        }
        return errno != ENOENT;
    }

    bool isFileCanBeRead(char fileName[]) {
        FILE *fp = fopen(fileName, "r");
        if (fp) {
            fclose(fp);
            return true;
        }
        return errno != ENOENT && errno != EPERM;
    }

0

测试文件是否存在的最快、最安全的方法不是单独/明确地进行测试,而是看看是否能找到一种替代普通方法的方式。

if(exists(file)) {                           /* point A */
    /* handle existence condition */
    return;
}

do_something_with(file);                     /* point B */

通过改进

r = do_something_with_unless_exists(file);

if(r == 0)
    success;
else if(errno == EEXIST)
    /* handle existence condition */
else
    /* handle other error */

除了更快,这还消除了第一种解决方案中固有的 竞争条件 (具体来说是 "TOC/TOU" ) 的可能性,即文件在A点和B点之间出现的可能性。
显然,第二个解决方案预设了以原子方式执行do_something_with_unless_exists操作的存在。通常有方法可以做到这一点,但有时需要四处搜索。
  • 创建文件:使用O_CREATO_EXCL调用open()

  • 在纯C中创建文件,如果您有C11:使用"wx"调用fopen()。(我昨天才了解到这个。)

  • 创建目录:只需调用mkdir()并检查errno == EEXIST

  • 获取锁定:任何值得其盐的锁定系统都已经具有原子获取锁定(只要没有其他人拥有它)的基本功能。

(还有其他方法,但这些是我现在能想到的。)

[注:在Unix早期,普通进程没有专门的设施可用于进行锁定,因此,如果您想设置互斥锁,则通常通过创建某个特定的空目录来实现,因为mkdir系统调用始终具有根据先前的存在或不存在而成功或失败的能力,具有原子性。]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接