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

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

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

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

说到这里,让我指出你的问题中的一些问题。你说你想要最快的方法,而且你有成千上万的文件,但是你要求一个函数的代码来测试一个单一的文件(这个函数只在c++中有效,而不是在C中)。一箱XY问题。你也可以说“in standard c++11(or)c++(or)c”…这些都是不同的,这也与你对速度的要求不一致……最快的解决方案是根据目标系统定制代码。问题中不一致的地方是,你接受的答案给出的解决方案是依赖于系统的,而不是标准C或c++。

我编写了一个测试程序,每个方法都运行了10万次,一半在存在的文件上,一半在不存在的文件上。

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

在5次运行中平均运行100,000个调用的总时间结果,

< span style=" font - family:宋体;"> < / th方法> < span style=" font - family:宋体;"< / th >时间> < span style=" font - family:宋体;"道明> > # EYZ0 (ifstream) < / . < span style=" font - family:宋体;">exists_test1 (FILE fopen) . txt文件 . < span style=" font - family:宋体;">exists_test2 (posix access()) . < span style=" font - family:宋体;">exists_test3 (posix stat())) .
0.485s
0.302s
0.202s
0.134s

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

我使用这段代码,到目前为止,它工作得很好。它没有使用c++的许多奇特的特性:

bool is_file_exist(const char *fileName)
{
std::ifstream infile(fileName);
return infile.good();
}
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.
}

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

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

备注:在c++ 14中,一旦文件系统TS完成并被采用,解决方案将使用:

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

从c++ 17开始,只有:

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

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

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

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

fstream file("file_name.txt");


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

我希望这对你有用。

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
}

如果你需要区分一个文件和一个目录,考虑下面这两个都使用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;
}

对于那些喜欢刺激的人:

 boost::filesystem::exists(fileName)

或者,自ISO c++ 17开始:

 std::filesystem::exists(fileName)

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

#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兼容系统的跨平台。

使用MFC可以实现以下功能

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

哪里FileName是一个字符串表示文件,您正在检查是否存在

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

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

enter image description here

enter image description here

引用自回答

在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语言,希望更快,也可以在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;
}

这里有一个简单的例子!

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

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

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

一个很好的启发:处理一堆已经拥有的数据比向操作系统请求任意数量的数据要快得多。系统调用开销相对于单个机器指令来说是巨大的。因此,让操作系统“给我这个目录下的全部文件列表”几乎总是会更快。然后在列表中挖掘,然后慢慢地要求操作系统“给我关于这个文件的信息”,“好,现在给我关于另一个文件的信息”,“现在给我关于……的信息”,等等。

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


如果我有这个要求,我会

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

除了取决于确切的用例,也许不是从哈希映射或树中删除条目,而是跟踪“我有这个文件吗?”布尔值,并计算出一个数据结构,使它能够O(1)来询问“我是否拥有每个文件?”。也许是二叉树,但每个非叶节点的结构也有一个逻辑布尔值,以及它的叶节点的布尔值。这可以很好地扩展——在一个叶节点中设置一个布尔值之后,你只需沿着树向上走,设置每个节点的“有这个吗?”布尔值为&&的子节点的布尔值(并且你不需要向下递归那些其他的子节点,因为如果你每次都这样做,当且仅当它们的所有子节点都为true时,它们将被设置为true。)


遗憾的是,在c++ 17之前没有标准方法来实现它。

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

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

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

然而,由于您正在寻找最快方式,您可能希望超越可移植/标准的东西。

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

在Windows上,经过一些挖掘,它看起来像为了获得最大的性能,你想要在可能的情况下使用FindFirstFileExFindExInfoBasicFIND_FIRST_EX_LARGE_FETCH,而许多开源库,比如上面的dirent.h,似乎都没有做到这一点。但是对于那些需要处理比前几个Windows版本更老的东西的代码,你最好直接使用没有额外标志的FindFirstFile

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

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


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

检测文件是否存在于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;
}

测试文件是否存在的最快和最安全的方法是根本不单独/显式地测试它。也就是说,看看你是否能找到一种方法来取代普通

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 /头"),即文件在点A和点B之间存在的可能性。

显然,第二个解决方案假定存在一种原子方法来执行do_something_with_unless_exists操作。通常总会有办法的,但有时你得四处寻找。

  • 创建文件:用O_CREATO_EXCL调用open()

  • 在纯C中创建一个文件,如果你有C11:调用fopen()"wx"。(我昨天才知道这个。)

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

  • 获取锁:任何称职的锁定系统都已经拥有一个原子的“只要没有其他人拥有就获取锁”原语。

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

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

还有一个更简单的方法

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