2012-06-26 243 views
11

我目前正在使用遍历各种目录的程序,以确保使用File.Exists()的特定文件存在。当路径太长时,File.Exists()错误地返回false

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

我知道有地址File.Exists()返回不正确的值的问题,但似乎没有解决这个特定的问题。

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

使用的代码没有什么特别的(我剪掉了一些不相关的代码),但是我会在下面包括它以防万一它有帮助。

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

它不返回'FALSE'不正确。它遵从文档:“如果在试图确定指定文件是否存在时发生任何错误,Exists方法将返回false,这可能会引发异常,例如传递文件名带有无效字符或太多字符,失败或缺少磁盘,或者调用者没有权限读取文件。“ –

+0

文件是否位于特殊文件夹中? – UrbanEsc

+0

你为什么在第一个地方检查?文件系统是不稳定的,所以你只是设置了竞争条件。在进行.Exists检查和使用文件之间删除文件时,您仍然必须准备好捕获异常,同时对.Exists()的调用会导致额外的(并且非常昂贵的)额外跳出到磁盘。最好把你的精力和代码放到这个异常处理程序中。 –

回答

9

这是丑陋和低效,但它绕过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."); 
    } 
} 
+0

这很好用!感谢您坚持这个问题! – Keplah

+1

请注意,总是假定你正在寻找一个文件,而不是目录。 –

12

MSDN - Naming Files, Paths, and Namespaces

在Windows API(在以下 段落中讨论一些例外),对于路径的最大长度为MAX_PATH,其被定义为260个字符 。

...

Windows API的有很多功能也使用Unicode版本 许可证的 32,767个字符的最大路径总长度的延长长度的路径。此类路径由用反斜杠分隔的组件 组成,每个组件由GetVolumeInformation 函数(此值通常为255个字符)的 lpMaximumComponentLength参数返回的值。 要指定 扩展长度路径,请使用"\\?\"前缀。例如,"\\?\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 t o禁用所有字符串解析,并将 之后的字符串直接发送到文件系统。例如,如果文件系统 支持较大的路径和文件名,则可以超出Windows API强制执行的MAX_PATH 限制。

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

更新:我发现了一个可以工作的解决方案,但它相当难看。下面是我的伪代码那样:

  1. 拆分的路径进入目录
  2. 数组
  3. 获取小于260个字符(MAX_PATH)的路径中最长的部分。
  4. 创建一个DirectoryInfo为您的路径的部分(“dir”为将来参考)。
  5. 对于路径中的其余目录:
    a。拨打dir.GetDirectories()并检查结果中是否包含下一个目录
    b。如果是,请将dir设置为DirectoryInfo并继续挖掘
    c。如果没有,那么路径不存在
  6. 一旦我们浏览了我们文件前的所有目录,请致电dir.GetFiles()并查看我们的文件是否存在于返回的FileInfo对象中。
+1

'File.Exist'支持'\?\\'路径吗?大多数.net API不。 – CodesInChaos

+0

我可以测试一下并找回你。 –

+0

感谢您的回复。我会给这个镜头。 – Keplah

5

从来没有自己的问题,在另一个SO帖子上的人建议打开文件的句柄,从而避免整个“存在”检查。不知道这仍然具有“长文件名”的问题:

这是第二个答案在这里:

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

不知道这是非常有用的:P

+1

你有点错过了作者的整个问题。他试图验证存在的东西,并且该方法返回false,他仍然必须找到该文件才能获取该文件的句柄。 –

+3

@Ramhound我想你错过了这个答案的重点。您可以尝试打开该文件,如果该文件不存在,则会抛出一个您可以处理和过滤的IOException(即“找不到文件”或“正在使用的文件”) –

+0

打开文件与检查它是否存在 - 如果文件无法打开,你会得到一个异常,唯一的区别是它*可能*避免长文件名问题,就像我说的,不知道它是否有用 – Charleh

2

您需要的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; 
     } 
    }