ChatGPT解决这个技术问题 Extra ChatGPT

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

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

inline bool exist(const std::string& name)
{
    /* SOMETHING */
}
boost::filesystem 似乎使用 stat()。 (从文档中假设。)我认为您不能为 FS 调用做得更快。让您快速完成工作的方法是“避免查看数千个文件”。
TOCTOU 问题:您如何知道文件在您的 exists() 检查和您的“对它做某事” 之间没有取消链接?
@pilcrow 好点,但是有相当广泛的应用程序不需要那么多正确性。例如 git push 可能不会费心确保您在初始脏检查后没有接触工作树。
“我想不出没有它的 C/C++ 实现”——Windows 不提供 POSIX 环境。

I
IInspectable

好吧,我拼凑了一个测试程序,将这些方法中的每一个运行了 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.485s exists_test1 (FILE fopen) 0.302s exists_test2 (posix access()) 0.202s exists_test3 (posix stat()) 0.134s

stat() 函数在我的系统(Linux,使用 g++ 编译)上提供了最佳性能,如果您出于某种原因拒绝使用 POSIX 函数,最好使用标准的 fopen 调用。


上述方法均不检查是否存在,而是检查可访问性。不过,我不知道有一种标准的 C 或 C++ 方法来检查是否存在。
stat() 似乎在检查是否存在。
任何使用它的人都需要记住#include 否则它会尝试使用错误的统计信息。
我想对于 ifstream 方法,您不需要 f.close() 因为 f 在函数末尾超出范围。那么 return f.good() 可以替换 if 块吗?
您还可以使用/测试即将推出的标准中的 en.cppreference.com/w/cpp/experimental/fs/exists
C
Community

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

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

从 C++17 开始,只有:

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

在 MS Visual Studio 2013 中,此函数在 std::tr2::sys::exists("helloworld.txt"); 下可用
我实际上希望它不会是 std::exists,那会很混乱(想想:像集合一样存在于 STL 容器中)。
同样在 Visual Studio 2015 中:#include <experimental/filesystem> bool file_exists(std::string fn) { std::experimental::filesystem::exists("helloworld.txt"); }
不要忘记#include <experimental/filesystem>
这适用于 Windows(c++17),但不适用于 linux(GCC C++17)。知道为什么吗?
h
harryngh

我使用这段代码,到目前为止它对我来说还可以。这并没有使用 C++ 的许多花哨功能:

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

但是,如果文件被另一个程序锁定或无法访问该文件,它可能会失败。
你需要关闭流吗?
@Mo0gles:ifstream 析构函数将在退出 is_file_exist 时被调用,它将关闭流。
@Orwellophile return std::ifstream(fileName);
@emlai 它应该是 return static_cast<bool>(std::ifstream(fileName));。如果没有 static_cast,编译器会抱怨。
a
anhoppe

对于那些喜欢提升的人:

 boost::filesystem::exists(fileName)

或者,从 ISO C++17 开始:

 std::filesystem::exists(fileName)

Boost 通常非常慢。
对于大多数应用程序,文件存在检查不是性能关键
并非高性能应用程序的所有方面都需要优化。例如,读取命令行或配置文件可能很复杂并且可能不需要速度,尽管应用程序本身可能需要 C++ 的性能优势。在这种情况下避免使用 Boost 构成了轮子改造,在反模式列表中名列前茅。
@SergeRogatch boost::filesystem::exists 并不是很慢。有关详细信息,请参阅我的基准测试结果。
“Boost 通常非常慢”——这是错误的,甚至不清楚声明的范围是什么...... Boost 包含许多不同作者的包,但经过了高质量的审查。 “对于大多数应用程序,文件存在检查不是性能关键” - 由于检查大量文件,OP 特别要求速度。 “如果性能不重要,那么使用 C++ 也没有意义”——另一个错误的评论(和题外话)。大多数软件都是在商店中编写的,并且是要求语言选择的系统的一部分。
C
Community

这取决于文件所在的位置。例如,如果它们都应该在同一个目录中,您可以将所有目录条目读入哈希表,然后根据哈希表检查所有名称。这可能在某些系统上比单独检查每个文件要快。单独检查每个文件的最快方法取决于您的系统...如果您正在编写 ANSI C,最快的方法是 fopen,因为它是唯一的方法(文件可能存在但不可打开,但您可能真的想要如果您需要“在其上做某事”,则可打开)。 C++、POSIX、Windows 都提供了额外的选项。

当我在这里时,让我指出你的问题的一些问题。您说您想要最快的方法,并且您有数千个文件,但随后您要求提供用于测试单个文件的函数的代码(并且该函数仅在 C++ 中有效,而不是 C)。这与您的要求相矛盾,因为它对解决方案做出假设...... the XY problem 的情况。您还说“在标准 c++11(or)c++(or)c 中”……它们都是不同的,这也与您对速度的要求不一致……最快的解决方案是根据目标系统。问题中的不一致通过您接受的答案突出显示,该答案提供了与系统相关且不是标准 C 或 C++ 的解决方案。


A
Alexander Huszagh

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

#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 的?
Mac OSX 是 POSIX 兼容的。
O
O'Neil

与 PherricOxide 建议的相同,但在 C 中

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

.c_str() 是一个 C++ 函数。我不知道 C++,所以我发布了一个 C 等价物。
S
Sam
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.
}

如果您真的要这样做,只需“返回(布尔)文件”而不是使用 if/else 分支。
万一发生真实情况,请不要忘记关闭文件。如果您在程序的整个运行时保持文件打开,这是一种内存泄漏,更不用说它可能会锁定您的文件,以便您在知道它存在后无法读取它。添加:file.close()到第二个。
再三考虑,也许您不需要显式关闭它...我忘记了 ifstream 是一个 RAII (资源获取是初始化)...并且会在超出析构函数的范围时自行清理...什么我可以说...这些天我被垃圾收集器语言洗脑了...
@BillMoore 您的第二条评论是正确的;此页面上的许多其他评论都指出 close() 不是必需的。
这会检查可访问性,而不是存在。例如,如果文件存在,但由于访问权限而无法访问,则返回false,误称文件不存在。
h
hungptit

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

r
ravin.wang

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 和 limited to 128 characters
GetFileAttributes 版本基本上是在 Windows 中执行此操作的规范方式。
我知道这很旧,但是在第三种情况下,当用户能够读取文件但不允许读取文件属性时会发生什么?
P
Praveen Kumar

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


正如公认的答案所示,这是不正确的。无论您是否输入 if ,任何认真的编译器都可能会发出相同的代码。与纯 C 变体相比,构造 ifstream 对象(即使在堆栈上)会产生额外的开销。
V
Valid

如果您需要区分文件和目录,请考虑以下两者都使用 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;
}

J
Jayhello

您可以使用std::ifstream,类似is_openfail的功能,例如如下代码(cout“打开”表示文件存在与否):

https://i.stack.imgur.com/8R1UV.png

https://i.stack.imgur.com/ujA6w.png

引用自此answer


m
mtraceur

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

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

一个很好的启发式方法:处理你已经拥有的一堆数据比向操作系统询问任何数量的数据要快得多。系统调用开销相对于单个机器指令来说是巨大的。因此,询问操作系统“给我这个目录中的整个文件列表”然后挖掘该列表几乎总是会更快,而询问操作系统“给我关于这个文件的信息”、“好的现在给我关于这个其他文件的信息”、“现在给我关于……的信息”等等。

每个好的 C 库都以一种有效的方式实现其“遍历目录中的所有文件”API,就像缓冲 I/O - 在内部,它一次从操作系统读取目录条目的大列表,即使 API 看起来像分别向操作系统询问每个条目。

所以如果我有这个要求,我会

尽一切可能鼓励设计和使用,以便所有文件都在一个文件夹中,并且该文件夹中没有其他文件,将我需要存在的文件名列表放入内存中具有 O( 1) 或至少 O(log(n)) 查找和删除时间(如哈希映射或二叉树),列出该目录中的文件,并在我从“ list”(哈希映射或二叉树)在内存中。

除了取决于确切的用例,也许不是从哈希映射或树中删除条目,我会跟踪“我有这个文件吗?”每个条目的布尔值,并找出一个数据结构,使 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 系统和任何其他 POSIX/SUS 系统上开箱即用。对于 Windows,您只需下载 dirent.h implementation 并将其放入包含路径中即可。

但是,由于您正在寻找最快的方法,您可能希望超越便携式/标准的东西。

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

在 Windows 上,经过一番挖掘,it looks like 为了获得最佳性能,您希望尽可能将 FindFirstFileExFindExInfoBasicFIND_FIRST_EX_LARGE_FETCH 一起使用,其中很多开源库都像上面的 dirent.h for Windows好像不行。但是对于需要使用比最后几个 Windows 版本更旧的东西的代码,您最好只使用直接的 FindFirstFile 而不使用额外的标志。

计划 9 不会包含在上述任何内容中,您将需要 dirreaddirreadall(如果您可以安全地假设您有足够的内存用于整个目录内容,则后者)。如果您想更好地控制缓冲区大小以提高性能,请使用普通 readread 并解码目录条目数据 - 它们采用与机器无关的记录格式,我认为提供了辅助函数。

我不知道任何其他操作系统。

稍后我可能会通过一些测试来编辑这个答案。欢迎其他人在测试结果中进行编辑。


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

其中 R 是您的路径类事物序列,exists() 来自未来的 std 或当前的提升。如果您自己动手,请保持简单,

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 没有这个限制。
GetFileAttributes 也仅限于 MAX_PATH。文档描述了一种解决方法如果您使用绝对路径、unicode,并在路径名称前附加一个特殊的前缀字符串。无论如何,我认为我们与特定于 Windows 的响应不谋而合。
GetFileAttributesW 没有限制。
A
Abhijeet Kandalkar

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

在 C++17 中,文件系统不再是实验性的
j
jack chyna

好吧,还有更简单的方法

#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;
}

如果文件存在但用户没有读取权限,这将无法正常工作。此外,它已经被另一个答案所涵盖。
K
KoopTheKoopa

这是一个简单的例子!

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

O
O'Neil

使用 MFC 可以通过以下方式

CFileStatus FileStatus;
BOOL bFileExists = CFile::GetStatus(FileName,FileStatus);

其中 FileName 是一个字符串,表示您正在检查是否存在的文件


I
Imad Diraa

只有一种更快的方法来检查文件是否存在以及您是否有权读取它使用 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;
    }

S
Steve Summit

测试文件存在的最快和最安全的方法是根本不单独/显式测试它。也就是看能不能找到办法代替普通的

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 */

除了更快之外,这还消除了第一个解决方案中固有的 race condition(特别是“TOC/TOU”),即文件存在于 A 点和 B 点之间的可能性。

显然,第二种解决方案的前提是存在执行 do_something_with_unless_exists 操作的原子方式。通常有一种方法,但有时你必须四处寻找它。

创建文件:使用 O_CREAT 和 O_EXCL 调用 open()。

如果您有 C11,则在纯 C 中创建文件:使用“wx”调用 fopen()。 (我昨天才知道这个。)

创建目录:只需调用 mkdir() 并在之后检查 errno == EEXIST。

获取锁:任何有价值的锁定系统都已经有一个原子的获取锁,因为它没有任何其他人拥有其他人拥有它的原语。

(还有其他的,但我现在能想到的就这些了。)

[脚注:在 Unix 的早期,并没有为普通进程提供特定的、专用的工具来进行锁定,所以如果你想设置一个互斥体,这通常是通过创建一个特定的空目录来实现的,因为 { 1} 系统调用总是有能力根据先前的存在或不存在原子地失败或成功。]


J
Juan Carlos

检测文件是否存在于windows中。

bool DoesExistFile(std::wstring filePath)
{
    bool result = true;

    HANDLE fileHandle = CreateFile(
        filePath.c_str(),
        GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);

    if ((fileHandle != NULL) && (fileHandle != INVALID_HANDLE_VALUE))
        CloseHandle(fileHandle);
    else
    {
        DWORD error = GetLastError();

        if ((error == ERROR_FILE_NOT_FOUND) || (error == ERROR_PATH_NOT_FOUND))
            result = false;
    }

    return result;
}

如果 Windows 中没有访问控制,这将起作用。就目前而言,访问控制是 Windows 中的核心安全原语。无论如何,如果您想要快速而没有太多误报,请使用 GetFileAttributes(请参阅 Superstition: Why is GetFileAttributes the way old-timers test file existence?)。
@IInspectable 感谢您的评论和链接,我非常感谢。在我的具体情况下,使用 Cloud Filter API,这是检查已删除占位符文件的现有父文件夹的唯一方法。我用 statGetFileAttributes 函数进行了测试,但它们不起作用。
CF API 是文件系统之上的虚拟化。这个问题询问的是文件系统,而不是建立在它之上的虚拟化。
无论如何,我希望能帮助面临我的问题或类似问题的人。
在这种情况下,您应该发布一个适用此建议答案的新问题。然后您可以answer your own question
m
miksiii

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

fstream file("file_name.txt");

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

希望这个对你有帮助。


如果文件不存在,此代码将创建文件,因此结果将始终为真。您需要使用 ifstream,或正确设置 openmode 参数。