【问题标题】:File.Exists() incorrectly returns false when path is too long当路径太长时 File.Exists() 错误地返回 false
【发布时间】:2012-06-26 15:11:00
【问题描述】:

我目前正在开发一个程序,该程序通过使用File.Exists() 来遍历各种目录以确保特定文件存在。

应用程序一直声称某些文件实际上不存在,而我最近发现这个错误是由于路径太长造成的。

我意识到在 SO 上存在一些问题,解决了 File.Exists() 返回不正确的值,但似乎没有一个可以解决这个特定问题。

重命名目录和文件以缩短路径并不是一个真正的选择,所以我现在不确定该怎么做。有没有办法解决这个问题?

使用的代码没什么特别的(我已经删掉了一些不相关的代码),但我会将它包含在下面以防万一。

    private void checkFile(string path)
    {
        if (!File.Exists(path))
            Console.WriteLine("   *  File: " + path + " does not exist.");
    }

【问题讨论】:

  • 它没有错误地返回false。它遵守文档:“如果在尝试确定指定文件是否存在时发生任何错误,Exists 方法将返回 false。这可能发生在引发异常的情况下,例如传递包含无效字符或过多字符的文件名、失败或缺少磁盘,或者调用者没有读取文件的权限。”
  • 文件是否位于特殊文件夹中?
  • 你为什么要先检查?文件系统是不稳定的,所以你只是在设置一个竞争条件。在进行 .Exists 检查和使用文件之间删除文件时,您仍然必须准备捕获异常,同时对 .Exists() 的调用会导致额外的(并且非常昂贵的)额外的磁盘行程.最好将您的精力和代码投入到异常处理程序中。
  • 好的,我找到了一个使用 DirectoryInfo 和 FileInfo 的解决方案。它不一定高效或漂亮,但它确实有效。

标签: c# .net


【解决方案1】:

来自MSDN - Naming Files, Paths, and Namespaces

在 Windows API 中(下面将讨论一些例外情况 段落),路径的最大长度为 MAX_PATH,即 定义为 260 个字符。

...

Windows API 有许多函数也有 Unicode 版本 允许一个扩展长度的路径,最大总路径长度为 32,767 个字符。这种类型的路径由组件组成 用反斜杠分隔,每个不超过返回的值 GetVolumeInformation 的 lpMaximumComponentLength 参数 函数(此值通常为 255 个字符)。 指定一个 扩展长度路径,使用 "\\?\" 前缀。例如,"\\?\D:\very long path"

...

因为您不能将"\\?\" 前缀与相对路径一起使用, 相对路径总是限制为总共 MAX_PATH 个字符。

(已添加重点)

如果您的所有路径都是完整路径,您可以更新代码以使用扩展长度路径说明符,如下所示:

const longPathSpecifier = @"\\?";

private void checkFile(string path)
{
    // Add the long-path specifier if it's missing
    string longPath = (path.StartsWith(longPathSpecifier) ? path : longPathSpecifier  + path);

    if (!File.Exists(longPath))
    {
        // Print the original path
         Console.WriteLine("   *  File: " + path + " does not exist.");
    }
}

更新:

对于文件 I/O,路径字符串的“\?\”前缀告诉 Windows 用于禁用所有字符串解析并发送后面的字符串的 API 它直接到文件系统。例如,如果文件系统 支持大路径和文件名,可以超过MAX_PATH Windows API 强制执行的限制。

至少在我的系统(使用 Windows 7)上,不支持长文件名,所以我无法验证上述解决方案是否适合您。

更新:我找到了一个可行的解决方案,但它相当难看。这是我在伪代码中所做的:

  1. 将路径拆分为目录数组
  2. 获取路径中少于 260 个字符的最长部分 (MAX_PATH)。
  3. 为路径的该部分创建一个DirectoryInfo(“dir”以供将来参考)。
  4. 对于路径中的其余目录:
    一种。调用dir.GetDirectories() 并检查下一个目录是否包含在结果中
    湾。如果是这样,请将 dir 设置为 DirectoryInfo 并继续挖掘
    C。如果不存在,则路径不存在
  5. 一旦我们浏览了指向我们文件的所有目录,调用dir.GetFiles() 并查看我们的文件是否存在于返回的FileInfo 对象中。

【讨论】:

  • File.Exist 是否支持 `\?\` 路径?大多数 .net API 没有。
  • 我可以测试一下并回复你。
  • 感谢您的回复。我会试一试。
  • 看起来File.Exists(path) 和新的FileInfo(path).Exists 都不适用于“\\?\”,至少对于我的文件系统(Windows 7)而言。
  • @JonSenchyna 这里也一样...好像不喜欢那样。
【解决方案2】:

这是丑陋和低效的,但它确实绕过了 MAX_PATH 限制:

const int MAX_PATH = 260;

private static void checkPath(string path)
{
    if (path.Length >= MAX_PATH)
    {
        checkFile_LongPath(path);
    }
    else if (!File.Exists(path))
    {
        Console.WriteLine("   *  File: " + path + " does not exist.");
    }
}

这里是 checkFile_LongPath 函数:

private static void checkFile_LongPath(string path)
{
    string[] subpaths = path.Split('\\');
    StringBuilder sbNewPath = new StringBuilder(subpaths[0]);
    // Build longest subpath that is less than MAX_PATH characters
    for (int i = 1; i < subpaths.Length; i++)
    {
        if (sbNewPath.Length + subpaths[i].Length >= MAX_PATH)
        {
            subpaths = subpaths.Skip(i).ToArray();
            break;
        }
        sbNewPath.Append("\\" + subpaths[i]);
    }
    DirectoryInfo dir = new DirectoryInfo(sbNewPath.ToString());
    bool foundMatch = dir.Exists;
    if (foundMatch)
    {
        // Make sure that all of the subdirectories in our path exist.
        // Skip the last entry in subpaths, since it is our filename.
        // If we try to specify the path in dir.GetDirectories(), 
        // We get a max path length error.
        int i = 0;
        while(i < subpaths.Length - 1 && foundMatch)
        {
            foundMatch = false;
            foreach (DirectoryInfo subDir in dir.GetDirectories())
            {
                if (subDir.Name == subpaths[i])
                {
                    // Move on to the next subDirectory
                    dir = subDir;
                    foundMatch = true;
                    break;
                }
            }
            i++;
        }
        if (foundMatch)
        {
            foundMatch = false;
            // Now that we've gone through all of the subpaths, see if our file exists.
            // Once again, If we try to specify the path in dir.GetFiles(), 
            // we get a max path length error.
            foreach (FileInfo fi in dir.GetFiles())
            {
                if (fi.Name == subpaths[subpaths.Length - 1])
                {
                    foundMatch = true;
                    break;
                }
            }
        }
    }
    // If we didn't find a match, write to the console.
    if (!foundMatch)
    {
        Console.WriteLine("   *  File: " + path + " does not exist.");
    }
}

【讨论】:

  • 这很完美!感谢您坚持这个问题!
  • 请注意,始终假定您正在查找文件,而不是目录。
【解决方案3】:

我自己从来没有遇到过这个问题,另一个 SO 帖子上的某个人建议打开文件的句柄,从而首先避免整个“存在”检查。不确定这是否仍然存在“长文件名”问题:

这里是第二个答案:

Check if a file/directory exists: is there a better way?

不确定这是否有用:P

【讨论】:

  • 您有点错过了作者遇到的整个问题。他正在尝试验证某些东西是否存在,并且该方法返回 false,他仍然必须找到该文件才能获得该文件的句柄。
  • @Ramhound 我认为你错过了这个答案的重点。您可以尝试打开该文件,如果该文件不存在,它将抛出一个您可以处理和过滤的 IOException(即“找不到文件”或“正在使用的文件”)
  • 打开文件和检查它是否存在是一回事——如果文件不能打开你会得到一个异常,唯一的区别是它可能避免长文件名问题,就像我说的,不知道有没有用
  • 使用FileFileInfo 打开文件与检查文件是否存在具有相同的路径长度限制。
  • 我认为可能是这样 - 就像我说的那样,可能没有用:D
【解决方案4】:

您需要 P/Invoke Win32 API 以使其正常工作:

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern uint GetFileAttributes(string lpFileName);

    public static bool DirectoryExists(string path)
    {
        uint attributes = GetFileAttributes(path.StartsWith(@"\\?\") ? path : @"\\?\" + path);
        if (attributes != 0xFFFFFFFF)
        {
            return ((FileAttributes)attributes).HasFlag(FileAttributes.Directory);
        }
        else
        {
            return false;
        }
    }

    public static bool FileExists(string path)
    {
        uint attributes = GetFileAttributes(path.StartsWith(@"\\?\") ? path : @"\\?\" + path);
        if (attributes != 0xFFFFFFFF)
        {
            return !((FileAttributes)attributes).HasFlag(FileAttributes.Directory);
        }
        else
        {
            return false;
        }
    }

【讨论】:

    【解决方案5】:

    检查

    1. 清单权限
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    
    1. 使用文件提供程序创建和访问文件
        <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="your_package.fileprovider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_list" />
        </provider>
    
    1. file_list 内容:
        <?xml version="1.0" encoding="utf-8"?>
        <paths>
            <external-path
                name="external"
                path="." />
            <external-files-path
                name="external_files"
                path="." />
            <cache-path
                name="cache"
                path="." />
            <external-cache-path
                name="external_cache"
                path="." />
            <files-path
                name="files"
                path="." />
        </paths>
    
    1. 保持文件名简短

    【讨论】:

      猜你喜欢
      • 2010-10-29
      • 1970-01-01
      • 2011-07-23
      • 2015-09-21
      • 1970-01-01
      • 1970-01-01
      • 2016-09-05
      • 1970-01-01
      • 2016-04-29
      相关资源
      最近更新 更多