我想找到最快的方法来检查一个文件是否存在于标准c++ 11, 14, 17,或C。我有成千上万的文件,在对它们做一些事情之前,我需要检查它们是否都存在。在下面的函数中,我可以写什么来代替/* SOMETHING */ ?

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

当前回答

我需要一个快速的函数,可以检查一个文件是否存在,PherricOxide的答案几乎是我所需要的,除了它没有比较boost::filesystem::exists和open函数的性能。从基准测试结果中,我们可以很容易地看到:

使用stat函数是检查文件是否存在的最快方法。注意,我的结果与PherricOxide的答案是一致的。 boost::filesystem::exists函数的性能与stat函数非常接近,并且具有可移植性。如果boost库可以从代码中访问,我会推荐这个解决方案。

在Linux内核4.17.0和gcc-7.3上获得的基准测试结果:

2018-05-05 00:35:35
Running ./filesystem
Run on (8 X 2661 MHz CPU s)
CPU Caches:
  L1 Data 32K (x4)
  L1 Instruction 32K (x4)
  L2 Unified 256K (x4)
  L3 Unified 8192K (x1)
--------------------------------------------------
Benchmark           Time           CPU Iterations
--------------------------------------------------
use_stat          815 ns        813 ns     861291
use_open         2007 ns       1919 ns     346273
use_access       1186 ns       1006 ns     683024
use_boost         831 ns        830 ns     831233

下面是我的基准代码:

#include <string.h>                                                                                                                                                                                                                                           
#include <stdlib.h>                                                                                                                                                                                                                                           
#include <sys/types.h>                                                                                                                                                                                                                                        
#include <sys/stat.h>                                                                                                                                                                                                                                         
#include <unistd.h>                                                                                                                                                                                                                                           
#include <dirent.h>                                                                                                                                                                                                                                           
#include <fcntl.h>                                                                                                                                                                                                                                            
#include <unistd.h>                                                                                                                                                                                                                                           

#include "boost/filesystem.hpp"                                                                                                                                                                                                                               

#include <benchmark/benchmark.h>                                                                                                                                                                                                                              

const std::string fname("filesystem.cpp");                                                                                                                                                                                                                    
struct stat buf;                                                                                                                                                                                                                                              

// Use stat function                                                                                                                                                                                                                                          
void use_stat(benchmark::State &state) {                                                                                                                                                                                                                      
    for (auto _ : state) {                                                                                                                                                                                                                                    
        benchmark::DoNotOptimize(stat(fname.data(), &buf));                                                                                                                                                                                                   
    }                                                                                                                                                                                                                                                         
}                                                                                                                                                                                                                                                             
BENCHMARK(use_stat);                                                                                                                                                                                                                                          

// Use open function                                                                                                                                                                                                                                          
void use_open(benchmark::State &state) {                                                                                                                                                                                                                      
    for (auto _ : state) {                                                                                                                                                                                                                                    
        int fd = open(fname.data(), O_RDONLY);                                                                                                                                                                                                                
        if (fd > -1) close(fd);                                                                                                                                                                                                                               
    }                                                                                                                                                                                                                                                         
}                                                                                                                                                                                                                                                             
BENCHMARK(use_open);                                  
// Use access function                                                                                                                                                                                                                                        
void use_access(benchmark::State &state) {                                                                                                                                                                                                                    
    for (auto _ : state) {                                                                                                                                                                                                                                    
        benchmark::DoNotOptimize(access(fname.data(), R_OK));                                                                                                                                                                                                 
    }                                                                                                                                                                                                                                                         
}                                                                                                                                                                                                                                                             
BENCHMARK(use_access);                                                                                                                                                                                                                                        

// Use boost                                                                                                                                                                                                                                                  
void use_boost(benchmark::State &state) {                                                                                                                                                                                                                     
    for (auto _ : state) {                                                                                                                                                                                                                                    
        boost::filesystem::path p(fname);                                                                                                                                                                                                                     
        benchmark::DoNotOptimize(boost::filesystem::exists(p));                                                                                                                                                                                               
    }                                                                                                                                                                                                                                                         
}                                                                                                                                                                                                                                                             
BENCHMARK(use_boost);                                                                                                                                                                                                                                         

BENCHMARK_MAIN();   

其他回答

windows下还有3个选项:

1

inline bool exist(const std::string& name)
{
    OFSTRUCT of_struct;
    return OpenFile(name.c_str(), &of_struct, OF_EXIST) != INVALID_HANDLE_VALUE && of_struct.nErrCode == 0;
}

2

inline bool exist(const std::string& name)
{
    HANDLE hFile = CreateFile(name.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != NULL && hFile != INVALID_HANDLE)
    {
         CloseFile(hFile);
         return true;
    }
    return false;
}

3

inline bool exist(const std::string& name)
{
    return GetFileAttributes(name.c_str()) != INVALID_FILE_ATTRIBUTES;
}

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

所有其他答案都侧重于单独检查每个文件,但如果所有文件都在一个目录(文件夹)中,那么读取目录并检查是否存在所需的每个文件名可能会更有效。

即使文件分布在几个目录中,这也可能更有效,这取决于目录与文件的确切比例。一旦您开始接近每个目标文件都在自己的目录中,或者在相同的目录中有许多其他文件,而您不想检查,那么我预计它最终会变得比单独检查每个文件更低效。

A good heuristic: working on a bunch of data you already have is much faster than asking the operating system for any amount of data. System call overhead is huge relative to individual machine instructions. So it is almost always going to be faster to ask the OS "give me the entire list of files in this directory" and then to dig through that list, and slower to ask the OS "give me information on this file", "okay now give me information on this other file", "now give me information on ...", and so on.

每个优秀的C库都以一种有效的方式实现了“遍历目录下的所有文件”api,就像缓冲I/O一样——在内部,它一次从操作系统中读取一个大的目录条目列表,即使这些api看起来像要求操作系统单独读取每个条目。


如果我有这个要求,我会

尽一切可能鼓励设计和使用,这样所有的文件都在一个文件夹里,没有其他文件在那个文件夹里, 将我需要呈现的文件名列表放入内存中的数据结构中,该数据结构具有O(1)或至少O(log(n))次查找和删除次数(就像哈希映射或二叉树), 列出该目录中的文件,并从内存中的“列表”(哈希映射或二叉树)中“检查”(删除)每个文件。

Except depending on the exact use case, maybe instead of deleting entries from a hash map or tree, I would keep track of a "do I have this file?" boolean for each entry, and figure out a data structure that would make it O(1) to ask "do I have every file?". Maybe a binary tree but the struct for each non-leaf node also has a boolean that is a logical-and of the booleans of its leaf nodes. That scales well - after setting a boolean in a leaf node, you just walk up the tree and set each node's "have this?" boolean with the && of its child node's boolean (and you don't need to recurse down those other child nodes, since if you're doing this process consistently every time you go to set one of the leaves to true, they will be set to true if and only if all of their children are.)


不幸的是,在c++ 17之前没有标准的方法来做这件事。

c++ 17得到std::filesystem::directory_iterator。

当然,有一个相应的boost::filesystem::directory_iterator,我认为它可以在旧版本的c++中工作。

最接近标准C方法的是opendir和readdir from direct .h。这是一个标准的C接口,它只是在POSIX中标准化,而不是在C标准本身中。它可以在Mac OS、Linux、所有bsd、其他UNIX/类UNIX系统和任何其他POSIX/SUS系统上开箱即用。对于Windows,有一个direct .h实现,你只需要下载并放入你的include路径。

然而,由于您正在寻找最快的方法,您可能想要超越便携式/标准的东西。

在Linux上,您可以通过使用原始系统调用getdents64手动指定缓冲区大小来优化性能。

在Windows上,经过一些挖掘,看起来为了获得最大的性能,你想要使用FindFirstFileEx与FindExInfoBasic和FIND_FIRST_EX_LARGE_FETCH,当你可以的时候,这是许多开源库,如上面的direct .h Windows似乎没有做到的。但是对于需要处理比前两个Windows版本更老的东西的代码,您不妨只使用直接的FindFirstFile,而不使用额外的标记。

上面的任何一种方法都不包括计划9,您将需要dirread或dirreadall(如果您可以安全地假设您有足够的内存用于整个目录内容,则选择后者)。如果您想要更好地控制缓冲区大小以提高性能,可以使用普通读取或读取并解码目录条目数据——它们是有文档记录的与机器无关的格式,而且我认为提供了辅助函数。

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


稍后我可能会用一些测试编辑这个答案。也欢迎其他人编辑测试结果。

在不使用其他库的情况下,我喜欢使用以下代码片段:

#ifdef _WIN32
   #include <io.h> 
   #define access    _access_s
#else
   #include <unistd.h>
#endif

bool FileExists( const std::string &Filename )
{
    return access( Filename.c_str(), 0 ) == 0;
}

这适用于Windows和posix兼容系统的跨平台。

虽然有几种方法可以做到这一点,但对您的问题最有效的解决方案可能是使用fstream的预定义方法之一,例如good()。使用此方法可以检查指定的文件是否存在。

fstream file("file_name.txt");

if (file.good()) 
{
    std::cout << "file is good." << endl;
}
else 
{
    std::cout << "file isnt good" << endl;
}

我希望这对你有用。