【问题标题】:When is it okay to check if a file exists?什么时候可以检查文件是否存在?
【发布时间】:2009-03-23 14:46:30
【问题描述】:

文件系统是易变的。这意味着您不能相信一个操作的结果对于下一个操作仍然有效,即使它是下一行代码。你不能只说if (some file exists and I have permissions for it) open the file,也不能说if (some file does not exist) create the fileif 条件的结果总是有可能在代码的两部分之间更改。操作是不同的:不是原子的。

更糟糕的是,问题的性质意味着,如果您想进行此检查,您很可能已经担心或意识到文件可能会发生您无法控制的事情。开发环境的性质使此事件在您的测试期间不太可能发生并且非常难以重现。因此,您不仅有错误,而且错误不会在测试时出现。

因此,在正常情况下,最好的做法是不要尝试检查文件或目录是否存在。相反,请将您的开发时间用于处理来自文件系统的异常。无论如何,您必须处理这些异常,因此这是对资源的更好利用。尽管异常很慢,但检查文件是否存在需要额外访问磁盘,并且磁盘访问速度要慢得多。在另一个问题中,我什至有一个投票良好的answer

但我有一些疑问。例如,在 .Net 中,如果确实总是正确,那么 .Exists() 方法一开始就不会出现在 API 中。还要考虑您期望程序需要创建文件的场景。想到的第一个例子是桌面应用程序。此应用程序将默认用户配置文件安装到它的主目录,并且每个用户第一次启动应用程序时,它会将这个文件复制到该用户的应用程序数据文件夹。它希望该文件在第一次启动时不存在。

那么,什么时候可以提前检查文件是否存在(或其他属性,如大小和权限)?在第一次尝试时期待失败而不是成功是一个足够好的经验法则吗?

【问题讨论】:

    标签: language-agnostic filesystems


    【解决方案1】:

    File.Exists 方法主要用于在您不打算打开文件时测试文件是否存在。例如,测试一个锁定文件的存在,它的存在告诉你一些事情,但其内容并不重要。

    如果您要打开该文件,那么无论之前调用 File.Exists 的结果如何,您都需要处理任何异常。所以,一般来说,在这种情况下调用它没有真正的价值。只需在您的 open 方法中使用适当的 FileMode 枚举值并处理任何异常,就这么简单。

    编辑:尽管这是根据 .Net API 表达的,但它基于底层系统 API。 Windows 和 Unix 都有使用 FileMode 枚举等效的系统调用(即 CreateFile)。事实上,在 .Net(或 Mono)中,FileMode 值只是传递给底层系统调用。

    【讨论】:

    • 我会补充一点,我经常使用 File.Exists。通常在“如果 !exist 从网络获取它作为 .tmp 然后修复文件名”模式。
    【解决方案2】:

    作为一般策略,File.Exists 之类的方法或WeakReference.AliveSomeConcurrentQueue.Count 之类的属性不能用作确保“良好”状态存在的方法,但可以用作确定存在的方法没有做任何不必要的(并且可能适得其反的)工作就存在“坏”状态。这种情况可能出现在许多涉及锁(和文件,因为它们通常包括锁)的场景中。因为所有需要锁定一组资源的例程都应该在可行的情况下始终以一致的顺序获取这些资源上的锁定,因此可能有必要在获取可能存在的资源之前获取预期存在的一个资源上的锁定或者可能不存在。在这种情况下,虽然无法避免可能会锁定第一个资源,但无法获取第二个资源,然后在没有对其进行任何有用的工作的情况下释放第一个锁,在此之前检查第二个资源的存在获得第一个锁定将最大程度地减少不必要和无用的工作。

    【讨论】:

    • @Adriano:几乎可以肯定。我的回复是在其他任何人之后很久才写的,但是因为他们都没有提到可以区分“对象 is 处于不良状态”和“对象 的“廉价”测试的有用性可能处于良好状态”,我认为值得写一个新答案。在检查 WeakReference 是否仍然存在时,存在某种类似的情况。该测试对于检查引用是否仍然有效,但它很有用,如果一个人想要执行一些操作,如果它不是。
    • @Adriano:如果希望使用WeakReference 执行某些操作(如果它仍然存在),则应将其Target 属性复制到一个变量并检查它是否为null';即使在复制和测试之间发生 GC,复制目标的行为也会保护对象不被收集。另一方面,如果只想在对象已被收集时执行某些操作,则应使用IsAlive 属性以避免意外延长对象的生命,一旦对象被回收,该对象将尽快死亡。不再使用。
    【解决方案3】:

    这取决于您的要求,但一种方法是尝试通过某种重试机制获取独占打开文件句柄。一旦有了这个句柄,另一个进程就很难(或不可能)删除(或移动)该文件。

    我在 .NET 中使用了与以下类似的代码来获取独占文件句柄,我希望其他进程可能会在其中写入文件:

    FileInfo fi = new FileInfo(fullFilePath);
    
    int attempts = maxAttempts;
    do
    {
        try
        {
            // Asking to open for reading with exclusive access...
            fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.None);
        }
        // Ignore any errors... 
        catch {}
    
        if (fs != null)
        {
            break;
        }
        else
        {
            Thread.Sleep(100);
        }
    }
    while (--attempts > 0);
    

    【讨论】:

    • 虽然这在 Windows 上是正确的,但在 *nix 上却不是。 Root,在许多情况下,普通用户,可以随意删除/移动文件,不管它们是如何打开的。通常它甚至不会影响手柄。当应用程序不放手时,Windows 文件锁定非常烦人......请不要再提倡它了。
    • @rmeador:别开玩笑了。有时需要独占锁!
    • 来自 Unix 背景,需要像 Windows 那样进行文件锁定的唯一原因是当您的系统认为文件名和文件不可分割时。 Unix 没有。
    【解决方案4】:

    一个例子:您可以检查是否存在无法打开的文件(例如,由于权限)。

    另一个可能更好的例子:你想检查一个 Unix 设备文件是否存在。但绝对不要打开它;打开它有副作用(例如,打开/关闭/dev/st0 将倒带)

    【讨论】:

      【解决方案5】:

      在 *nix 环境中,检查程序的另一个副本是否已经在运行的一种完善的方法是创建一个锁定文件。因此,检查文件是否存在用于验证这一点。

      【讨论】:

      • 你通常不会打开 ( ... O_CREAT|O_EXCL ) 因为你想在不存在的情况下获得锁吗?
      • @derobert:不过,在这种情况下,*NIX 正在检查程序是否正在运行。通过 O_CREAT|O_EXCL 使用 open 可以很好地检查文件是否存在。
      • 检查一个程序是否正在运行需要更多的时间——你实际上必须打开 pid 文件,读取它,并检查该进程是否仍然存在。所以你只需要在没有 O_CREAT 的情况下打开。
      【解决方案6】:

      只有在我预计它会丢失(例如应用程序设置)并且只有在我必须读取文件时才会检查它。

      如果我必须写入文件,它要么是一个日志文件(所以我可以附加到它或创建一个新的)或者我替换它的内容,所以我还是重新创建它。

      如果我期望该文件存在,那么抛出异常是正确的。然后异常处理应通知用户或执行恢复。我的观点是,这会产生更简洁的代码。

      文件保护(即不覆盖(可能很重要)文件)是不同的,在这种情况下,如果框架不为我这样做,我总是会检查文件是否存在(想想 SaveFileDialog)

      【讨论】:

        【解决方案7】:

        我认为当您首先要确保文件存在时,检查是有意义的。正如你所说的设置文件...如果有文件我会尝试合并现有的设置而不是把它们吹走。

        其他情况是用户告诉我对文件执行某些操作。是的,我知道 openFileDialog 将检查文件是否存在(但这是可选的)。我模糊地记得在 VB6 中情况并非如此,因此验证他们刚刚告诉我使用的文件是否存在是很常见的。

        我宁愿不进行异常编程。

        编辑

        我没有错过重点。您可能会尝试访问该文件,然后引发异常,然后当您创建该文件时,该文件已经放置在那里。现在这会导致您的异常处理代码出现问题。所以我想我们可以在我们的异常处理程序中有一个异常处理程序来捕获文件再次更改......

        我宁愿尝试阻止异常,而不是使用它们来控制逻辑。

        编辑

        另外一次检查诸如大小之类的属性是在您等待文件操作完成时,是的,您永远无法确定,但使用好的算法并且取决于写入文件的系统,您可能能够处理很多情况(有一个运行了五年的系统,它监视来自 ftp 的小文件,它使用与文件系统监视程序相同的 api,然后开始轮询等待文件停止更改,然后再引发事件文件已准备好使用)。

        【讨论】:

        • +1 - 我将删除我的答案以支持你的答案,我认为这抓住了问题的本质:)
        • 你错过了重点:如果你想确定文件在那里,检查文件然后使用它的操作不是原子的。期间可能会发生一些事情。
        【解决方案8】:

        这可能太简单了,但我认为检查文件是否存在(因此存在 .Exists())的主要原因是防止意外覆盖现有文件,而不是避免由试图访问不存在或不可访问的文件。

        编辑 2

        事实上,这太简单了,我建议你看看斯蒂芬马丁的回应。

        【讨论】:

        • 不确定 .Net API,但肯定有类似 Unix 的 O_EXCL|O_CREAT 的东西(创建文件,但前提是它不存在)。这是原子的,所以它不会被在测试中间创建文件的人所愚弄。
        • 可能,但至少对于 System.IO.File 类,如果您调用 Create 并且文件已经存在,它将在没有通知或异常的情况下覆盖它(除非抛出另一个异常,例如文件是只读的)
        • @cmsjr:这取决于您调用的方法和使用的参数。 @derobert:我宁愿不按异常编程,如果可以的话,尽量避免它们。例如,如果我想避免覆盖配置文件,检查它是否存在是一个很好的步骤。必须比尝试创建它并捕获它更好
        • 不好:文件可能会在您检查和创建文件之间创建。
        • 是的,但我认为,与 SomeIrretrievableAndImportantData.zip 之类的文件相比,可以在 .Exists 和 .Create 之间创建足够不稳定的文件是可以覆盖的文件。
        【解决方案9】:

        如果您担心其他人会删除该文件,也许您应该实施某种锁定系统。例如,我曾经为 Usenet 新闻服务器 C-News 编写代码。由于它所做的很多事情都可能异步发生,它会通过制作一个临时文件来“锁定”一个文件或目录,然后将其硬链接到一个名为“LOCK”的文件。如果链接失败,则意味着其他版本的程序正在写入该目录,否则它是你的,你可以做你喜欢的。

        这方面的妙处在于大部分程序都是用 shell 和 awk 编写的,这是一种非常便携的锁定机制。此外,锁定文件将包含所有者的 PID,因此您可以查看现有的锁定文件以查看所有者是否仍在运行。

        【讨论】:

          【解决方案10】:

          我们有一个诊断工具,它必须收集一组文件,包括安装程序日志。根据不同的条件,安装程序日志可以位于两个文件夹之一中。更糟糕的是,这两个文件夹中可能有不同版本的日志。该工具如何找到合适的?

          如果您检查是否存在,这很简单。如果只有一个,请获取该文件。如果存在两个,则查找哪个具有最新的修改时间并获取该文件。这只是正常的做事方式。

          【讨论】:

            【解决方案11】:

            虽然这是一篇与语言无关的帖子,但您似乎在谈论 .NET。大多数系统(.NET 和其他系统)都有更详细的 API,以便在打开文件时确定文件是否存在。

            您应该做的是调用以访问该文件,因为它通常会通过某种错误指示该文件不存在(如果它确实不存在)。在 .NET 中,您必须通过 P/Invoke 层并使用 CreateFile API 函数。如果该函数返回 ERROR_FILE_NOT_FOUND 错误,那么您就知道该文件不存在。如果返回成功,那么你就有了一个可以使用的句柄。

            这里的重点是它是一个有点的原子操作,这最终就是你要找的。​​p>

            然后,使用句柄,您可以将其传递给 FileStream 构造函数并在文件上执行您的工作。

            【讨论】:

            • 呃,至少现在,这个问题被标记为与语言无关。所以假设 .NET 没有多大意义。
            • 不,他说得很好。我确实来自 .Net 背景,.Net 构成了我的文件系统 API 经验的大部分(不是全部)。但是,我确实觉得问题延伸到了 .Net 之外。
            • @unwind & Joel Coehoorn:我已经更新了帖子,使其更加与语言无关。
            【解决方案12】:

            您可能正在编写许多可能的应用程序,简单的 File.Exists 足以胜任这项工作。如果它是一个只有您的应用程序会使用的配置文件,那么您不需要在异常处理中如此矫枉过正。

            虽然您在使用此方法时指出的“缺陷”都是有效的,但这并不意味着它们在某些情况下不是可接受的缺陷。

            【讨论】:

              【解决方案13】:

              各种应用都包括内置的网络服务器。他们第一次启动时生成自签名 SSL 证书是很常见的。实现这一点的一种直接方法是在启动时检查证书是否存在,如果不存在则创建它。

              理论上,它可以为检查而存在,以后不存在。在这种情况下,当我们尝试收听时会出现错误,但这可以很容易地处理并且不是什么大问题。

              也有可能是检查时不存在,以后存在。在这种情况下,它要么被新证书覆盖,要么写入新证书失败,具体取决于您的策略。第一个有点烦人,证书更改会引起一些警报,但也不是很关键,特别是如果您进行一些日志记录以指示正在发生的事情。

              而且,在实践中,这两种情况都极不可能出现。

              【讨论】:

              • 为什么要检查文件是否存在,而不是仅仅打开文件来读取证书和私钥(如果打开失败则处理失败)?
              【解决方案14】:

              就像您指出的那样,如果文件丢失,程序应该做什么总是很重要的。在我的所有应用程序中,用户始终可以删除配置文件,应用程序将使用默认值创建一个新文件。没问题。我也发布了没有配置文件的应用程序。

              但是用户倾向于删除文件,甚至是他们不应该删除的文件,如序列号和模板文件。我总是检查这些文件,因为没有它们,应用程序根本无法运行。我无法默认创建新的序列号。

              文件丢失时会发生什么?您可以执行文件查找或异常处理程序,但真正的问题是:文件丢失时会发生什么?或者文件对应用程序有多重要。在尝试访问应用程序的任何支持文件之前,我一直在检查。另外,如果文件损坏且无法加载,我会进行错误处理。

              【讨论】:

                【解决方案15】:

                我认为,只要您知道文件可能存在也可能不存在,并且您想根据文件的存在执行一些替代操作,您应该进行检查,因为在这种情况下,这不是文件的例外情况不存在。这不会免除您必须处理异常的责任——其他人在检查和打开之间删除或创建文件——但它使程序的意图清晰,并且不依赖异常处理来执行流程-控制逻辑。

                编辑:一个例子可能是启动时的日志轮换。

                  try
                  {
                       if (File.Exists("app.log"))
                       {
                           RotateLogs();
                       }
                
                       log = File.Open("app.log", FileMode.CreateNew );
                  }
                  catch (IOException)
                  {
                     ...another writer, perhaps?
                  }
                  catch (UnauthorizedAccessException)
                  {
                     ...maybe I should have used runas?
                  }
                

                【讨论】:

                • 这使代码量增加了一倍,并添加了一个很少使用的错误路径。对我来说,听起来很可能会产生错误。
                • 没有。只需将 try/catch 作为外部块,根据需要捕获不同的异常类型,然后在 try 块内执行条件逻辑。这个加倍代码如何 - 你已经想根据我的场景中的存在做一些不同的事情?
                【解决方案16】:

                为了回答我自己的问题(部分),我想扩展我使用的示例:默认配置文件。

                与其在应用启动时检查它是否存在并在检查失败时尝试复制文件,不如始终尝试复制文件。您只需这样做,如果文件存在,副本将失败,而不是替换现有文件。这样,您只需捕获并忽略由于现有文件导致复制失败时引发的任何异常。

                【讨论】:

                  【解决方案17】:

                  您的问题可以通过基本的计算机科学轻松解决...阅读Semaphores

                  (我的意思不是听起来像个混蛋,我只是为您指出一个常见问题的简单答案)。

                  【讨论】:

                    【解决方案18】:

                    我认为“存在”的原因是确定文件何时丢失,而无需创建访问文件所需的所有操作系统内务数据或引发异常。所以它是一个文件处理优化比什么都重要。

                    对于单个文件,“Exists”给出的保存通常是微不足道的。如果您要检查一个文件是否存在很多次(例如,搜索#include 文件),那么节省的时间可能会很大。

                    在 .Net 中,File.Exists 的规范没有列出该方法可能抛出的任何异常,这与 File.Open 列出九个异常不同,因此前者的检查肯定更少。

                    即使“Exists”返回 true,您仍然需要在打开文件时处理异常,正如 .Net 参考所建议的那样。

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-06-16
                      • 2011-01-17
                      • 2013-06-20
                      • 2018-01-31
                      • 2011-02-24
                      • 1970-01-01
                      相关资源
                      最近更新 更多