使用标准的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个回答

1013

我编写了一个测试程序,分别对存在的文件和不存在的文件运行这些方法,并且每个方法都运行了100,000次。

#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <fstream>

inline bool exists_test0 (const std::string& name) {
    ifstream f(name.c_str());
    return f.good();
}

inline bool exists_test1 (const std::string& name) {
    if (FILE *file = fopen(name.c_str(), "r")) {
        fclose(file);
        return true;
    } else {
        return false;
    }   
}

inline bool exists_test2 (const std::string& name) {
    return ( access( name.c_str(), F_OK ) != -1 );
}

inline bool exists_test3 (const std::string& name) {
  struct stat buffer;   
  return (stat (name.c_str(), &buffer) == 0); 
}

总共运行 100,000 次调用的时间结果,平均分成 5 组如下:

方法 时间
exists_test0 (使用 ifstream) 0.485 秒
exists_test1 (使用 FILE fopen) 0.302 秒
exists_test2 (使用 posix access()) 0.202 秒
exists_test3 (使用 posix stat()) 0.134 秒

在我的系统上(Linux,编译器为 g++),stat() 函数表现最佳。如果您出于某种原因拒绝使用 POSIX 函数,则标准的 fopen 调用是您最好的选择。


53
以上方法都是检查可访问性而非存在性。我不知道任何一种标准的 C 或 C++ 方法可以用来检查存在性。 - IInspectable
15
stat() 似乎是用来检查文件是否存在。 - el.pescado - нет войне
128
任何使用此代码的人需要记住要 #include <sys/stat.h>,否则它会尝试使用错误的 stat 函数。 - Katianie
24
对于使用 ifstream 方法,你不需要使用 f.close() ,因为在函数结束时 f 将超出作用域。所以 return f.good() 可以替换掉 if 代码块,这样做是可行的。 - ilent2
14
你也可以使用/测试来自即将出版的标准的http://en.cppreference.com/w/cpp/experimental/fs/exists。 - zahir
显示剩余15条评论

286

注意:在C++14中并且一旦filesystem TS完成并被采纳,解决方案将是使用:

std::experimental::filesystem::exists("helloworld.txt");

而自C++17以来,仅限于:

std::filesystem::exists("helloworld.txt");

3
我希望它不会是 std::exists,那样会相当令人困惑(类似于在 STL 容器中的 set 中存在) 。 - einpoklum
4
在Visual Studio 2015中,还可以这样写:#include bool file_exists(std::string fn) { return std::experimental::filesystem::exists(fn); } - Orwellophile
5
别忘了加上#include <experimental/filesystem> - Mohammed Noureldin
1
这在我的Windows(c++17)上可以工作,但在Linux(GCC C++17)下无法工作。有什么想法为什么? - willem
4
对于在Linux上使用C++17选项的人,不要忘记在你的CMakeLists.txt中添加stdc++fs库。例如:target_link_libraries(<可执行文件名称> stdc++fs) - biendltb
显示剩余3条评论

139

我使用这段代码,到目前为止它运行良好。它并没有使用C++的很多花哨特性:

bool is_file_exist(const char *fileName)
{
    std::ifstream infile(fileName);
    return infile.good();
}

10
然而,如果文件被其他程序锁定或者文件无法访问,这种方法可能会失败。 - Jet
4
你需要关闭流吗? - Mo0gles
37
ifstream 析构函数将在 is_file_exist 结束时被调用,并关闭流。 - Isaac
8
@Orwellophile return std::ifstream(fileName); 的意思是返回一个打开指定文件的输入流对象。 - emlai
5
应该这样写:return static_cast<bool>(std::ifstream(fileName));。如果没有static_cast,编译器会报错。 - user4223038
显示剩余3条评论

51

对于喜欢增加速度的人:

boost::filesystem::exists(fileName)

或者,自ISO C++17起:
std::filesystem::exists(fileName)

7
"Boost通常非常缓慢。" - Serge Rogatch
7
对于大多数应用程序而言,文件存在检查并不是性能关键。 - anhoppe
38
并非高性能应用的所有方面都需要优化。例如,读取命令行或配置文件可能很复杂,可能不需要速度,尽管应用程序本身可能需要C++的性能优势。在这种情况下避免使用Boost属于重新发明轮子,这是反模式列表中的高风险项目。 - evoskuil
7
boost::filesystem::exists 不是非常缓慢。请查看我的基准测试结果以获取详细信息。 - hungptit
8
“Boost通常非常慢”这种说法是错误的,而且甚至不清楚这个声明的范围是什么……Boost包含许多来自不同作者的软件包,但已经经过高质量的审查。 “对于大多数应用程序来说,文件存在检查并不是性能关键点” ── 由于要检查大量文件,OP特别要求速度。 “如果性能不关键,则使用C ++也没有意义” ── 另一个错误的评论(并且离题)。大多数软件是在“商店”中编写的,并且是强制语言选择的“系统”的一部分。 - Jim Balter
显示剩余2条评论

30

这取决于文件所在位置。例如,如果它们都应该在同一个目录中,则可以将所有目录条目读入哈希表中,然后检查所有名称是否与哈希表匹配。这种方法在某些系统上可能比逐个检查每个文件更快。逐个检查每个文件的最快方法取决于您的系统……如果您正在编写ANSI C,最快的方法是使用fopen,因为这是唯一的方法(文件可能存在但无法打开,但如果您需要“对其进行操作”,则您可能确实希望能够打开)。C ++、POSIX、Windows都提供了其他选项。

在此,让我指出您问题中存在的一些问题。您说您想要最快的方式,并且有数千个文件,但是您又要求编写一个测试单个文件的函数(该函数仅适用于C++,而不是C)。这与您的要求相矛盾,因为它对解决方案做出了假设...这是XY问题的一个例子。您还说“在标准c++11(或c++或c)中”...这些都是不同的,这也与您对速度的要求不一致...最快的解决方案将涉及将代码定制到目标系统。问题的不一致之处在于您接受了一个给出依赖于系统且不是标准C或C ++的解决方案的答案。

28

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

#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 标准的系统。


这个在Mac上能用吗?我没有Mac,但我认为Mac应该也能包含unistd.h。也许第一个#ifdef应该是特定于Windows的? - matth
5
Mac OSX 是符合 POSIX 标准的。 - schaiba

25

按照PherricOxide的建议,但使用C语言。

#include <sys/stat.h>
int exist(const char *name)
{
  struct stat   buffer;
  return (stat (name, &buffer) == 0);
}

1
".c_str()是C++函数。我不懂C++,所以我发了一个等价的C语言版本。" - Ramon La Pietra

11

我需要一个快速的函数来检查文件是否存在。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();   

10
inline bool exist(const std::string& name)
{
    ifstream file(name);
    if(!file)            // If the file was not found, then file is 0, i.e. !file=1 or true.
        return false;    // The file was not found.
    else                 // If the file was found, then file is non-0.
        return true;     // The file was found.
}

24
如果你真的要这样做,只需使用“return (bool)file”而不是使用if / else分支语句。 - Nik Haldimann
在真实情况下,不要忘记关闭文件。如果您在程序的整个运行时保持文件处于打开状态,则会出现内存泄漏类型,更不用说它可能会锁定您的文件,以至于您无法在知道其存在后读取它。在第二个else中添加:file.close()。 - Bimo
2
转念一想,也许你不需要显式地关闭它...我忘记了 ifstream 是一个 RAII(资源获取即初始化)...并且将在析构函数中清理自己...我能说什么呢...这些天我被垃圾收集器语言洗脑了... - Bimo
@BillMoore 您的第二条评论是正确的;本页面上许多其他评论都指出 close() 不是必需的。 - Keith M
1
这个检查的是可访问性,而不是存在性。例如,如果文件存在但由于访问权限而无法访问,则它将返回false,错误地声称该文件不存在。 - SasQ

7
另外在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;
}

OpenFile仅支持ANSI编码,且文件名长度限制为128个字符 - David Bremner
6
“GetFileAttributes” 版本基本上是在 Windows 上执行此操作的规范方法。 - Felix Dombek
我知道这很老套,但是当用户有读取文件的权限但是不允许读取文件属性时,在第三种情况下会发生什么? - Quest

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