2009-08-19 172 views
13

我有一个String形式的文件路径。在Java中,我需要确定文件系统上是否存在该文件(并且我们的代码需要跨平台,因为它在Windows,Linux和OS X上运行)。区分大小写File.equals区分大小写的文件系统

问题是文件路径和文件本身的情况可能不匹配,即使它们确实代表相同的文件(大概这是因为它们起源于Windows并且未注意到差异)。

例如,我有一个文件路径“ABC.txt”。文件系统上存在名为“abc.txt”的文件。下面的代码将返回在Linux在Windows 真正

new File("ABC.txt").exists(); 

是什么,以确定是否存在该文件的最好方法,如果它存在于返回的句柄文件上的文件系统?

回答

13

从目录中获取文件列表(File.list())并使用equalsIgnoreCase()比较名称。

+0

是的,这是一个可能性,但如果路径中的目录是错误的情况下问题仍然存在。该解决方案可能最终会成为某种递归算法,它会在目录树上执行不区分大小写的搜索。但我希望有更好的解决方案! – jwaddell 2009-08-19 05:06:59

+1

@jwaddell:我不认为有更好的解决方案,因为文件名/路径可以在任何shell中,并且linux操作系统将其视为区分大小写的模式。 – 2009-08-19 05:20:48

+0

看起来我会不情愿地实施这个解决方案,加上递归路径检查。 – jwaddell 2009-08-19 22:34:30

1

如果差异是随机的,那么Shimi的解决方案包括递归路径段检查是最好的解决方案。这听起来很丑陋,但你可以隐藏在一个单独的类中的魔法,并实现一个简单的API来返回给定文件名的文件句柄,所以你只需要看到类似Translator.translate(file)的调用。

也许,差异是一种静态的,可预测的。然后,我更喜欢可以用来将给定文件名转换为Windows/Linux文件名的字典。这比其他方法有很大的优势:获得错误文件句柄的风险较小。

如果字典真的是静态的,您可以创建并维护一个属性文件。如果它是静态的但更复杂的话,假定一个给定的文件名可以被翻译成多个可能的目标文件名,我会用一个Map<String, Set<String>>数据结构(Set优先于List,因为没有重复的替代)备份冠词类。

+0

不幸的是,文件名可能是任何东西。 – jwaddell 2009-08-19 22:33:01

2

正如jwaddell所说,它看起来像非常慢的递归路径检查是(显然)唯一的方法来做到这一点。这里是我用java写的函数,它接受一个字符串,它是一个文件路径。如果文件路径的字符串表示存在并且与Windows报告的大小写相同,则返回true,否则返回false。

public boolean file_exists_and_matches_case(
     String full_file_path) { 

    //Returns true only if: 
    //A. The file exists as reported by .exists() and 
    //B. Your path string passed in matches (case-sensitivity) the entire 
    // file path stored on disk. 

    //This java method was built for a windows file system only, 
    //no guarantees for mac/linux/other. 
    //It takes a String parameter like this: 
    //"C:\\projects\\eric\\snalu\\filename.txt" 
    //The double backslashes are needed to escape the one backslash. 

    //This method has partial support for the following path: 
    //"\\\\yourservername\\foo\\bar\\eleschinski\\baz.txt". 
    //The problem is it stops recusing at directory 'foo'. 
    //It ignores case at 'foo' and above. So this function 
    //only detects case insensitivity after 'foo'. 


    if (full_file_path == null) { 
     return false; 
    } 

    //You are going to have to define these chars for your OS. Backslash 
    //is not specified here becuase if one is seen, it denotes a 
    //directory delimiter: C:\filename\fil\ename 
    char[] ILLEGAL_CHARACTERS = {'/', '*', '?', '"', '<', '>', '>', '|'}; 
    for (char c : ILLEGAL_CHARACTERS) { 
     if (full_file_path.contains(c + "")) { 
      throw new RuntimeException("Invalid char passed in: " 
        + c + " in " + full_file_path); 
     } 
    } 

    //If you don't trim, then spaces before a path will 
    //cause this: 'C:\default\ C:\mydirectory' 
    full_file_path = full_file_path.trim(); 
    if (!full_file_path.equals(new File(full_file_path).getAbsolutePath())) 
    { 
     //If converting your string to a file changes the directory in any 
     //way, then you didn't precisely convert your file to a string. 
     //Programmer error, fix the input. 
     throw new RuntimeException("Converting your string to a file has " + 
      "caused a presumptous change in the the path. " + full_file_path + 
      " to " + new File(full_file_path).getAbsolutePath()); 
    } 

    //If the file doesn't even exist then we care nothing about 
    //uppercase lowercase. 
    File f = new File(full_file_path); 
    if (f.exists() == false) { 
     return false; 
    } 

    return check_parent_directory_case_sensitivity(full_file_path); 
} 

public boolean check_parent_directory_case_sensitivity(
     String full_file_path) { 
    //recursively checks if this directory name string passed in is 
    //case-identical to the directory name reported by the system. 
    //we don't check if the file exists because we've already done 
    //that above. 

    File f = new File(full_file_path); 
    if (f.getParent() == null) { 
     //This is the recursion base case. 
     //If the filename passed in does not have a parent, then we have 
     //reached the root directory. We can't visit its parent like we 
     //did the other directories and query its children so we have to 
     //get a list of drive letters and make sure your passed in root 
     //directory drive letter case matches the case reported 
     //by the system. 

     File[] roots = File.listRoots(); 
     for (File root : roots) { 
      if (root.getAbsoluteFile().toString().equals(
        full_file_path)) { 
       return true; 
      } 
     } 
     //If we got here, then it was because everything in the path is 
     //case sensitive-identical except for the root drive letter: 
     //"D:\" does not equal "d:\" 
     return false; 

    } 

    //Visit the parent directory and list all the files underneath it. 
    File[] list = new File(f.getParent()).listFiles(); 

    //It is possible you passed in an empty directory and it has no 
    //children. This is fine. 
    if (list == null) { 
     return true; 
    } 

    //Visit each one of the files and folders to get the filename which 
    //informs us of the TRUE case of the file or folder. 
    for (File file : list) { 
     //if our specified case is in the list of child directories then 
     //everything is good, our case matches what the system reports 
     //as the correct case. 

     if (full_file_path.trim().equals(file.getAbsolutePath().trim())) { 
      //recursion that visits the parent directory 
      //if this one is found. 
      return check_parent_directory_case_sensitivity(
        f.getParent().toString()); 
     } 
    } 

    return false; 

} 
6

如果与有问题的确切名称的文件存在(路径部分不区分大小写)这个方法会告诉你。

public static boolean caseSensitiveFileExists(String pathInQuestion) { 
    File f = new File(pathInQuestion); 
    return f.exists() && f.getCanonicalPath().endsWith(f.getName()); 
} 
+1

对于我的示例,这仍然会在Linux上返回false,因为f.exists()将返回false。 – jwaddell 2011-10-26 06:46:50

+3

+1 不完全是主题问题的答案,但帮助我在​​Windows上执行区分大小写的检查。 – Dmitry 2012-06-13 09:37:25

1

这里是我的Java 7解决方案,适用于已知父路径和相对子路径可能与磁盘路径不同的情况。

例如,给定的文件/tmp/foo/biscuits,该方法将正确地返回一个Path到文件具有以下输入:

  • /tmpfoo/biscuits
  • /tmpfoo/BISCUITS
  • /tmpFOO/BISCUITS
  • /tmp and FOO/biscuits

请注意,此解决方案有而不是已经过严格测试,因此应该将其视为起点而非生产就绪片段。

/** 
* Returns an absolute path with a known parent path in a case-insensitive manner. 
* 
* <p> 
* If the underlying filesystem is not case-sensitive or <code>relativeChild</code> has the same 
* case as the path on disk, this method is equivalent to returning 
* <code>parent.resolve(relativeChild)</code> 
* </p> 
* 
* @param parent parent to search for child in 
* @param relativeChild relative child path of potentially mixed-case 
* @return resolved absolute path to file, or null if none found 
* @throws IOException 
*/ 
public static Path getCaseInsensitivePath(Path parent, Path relativeChild) throws IOException { 

    // If the path can be resolved, return it directly 
    if (isReadable(parent.resolve(relativeChild))) { 
     return parent.resolve(relativeChild); 
    } 

    // Recursively construct path 
    return buildPath(parent, relativeChild); 
} 

private static Path buildPath(Path parent, Path relativeChild) throws IOException { 
    return buildPath(parent, relativeChild, 0); 
} 

/** 
* Recursively searches for and constructs a case-insensitive path 
* 
* @param parent path to search for child 
* @param relativeChild relative child path to search for 
* @param offset child name component 
* @return target path on disk, or null if none found 
* @throws IOException 
*/ 
private static Path buildPath(Path parent, Path relativeChild, int offset) throws IOException { 
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(parent)) { 
     for (Path entry : stream) { 

      String entryFilename = entry.getFileName().toString(); 
      String childComponent = relativeChild.getName(offset).toString(); 

      /* 
      * If the directory contains a file or folder corresponding to the current component of the 
      * path, either return the full path (if the directory entry is a file and we have iterated 
      * over all child path components), or recurse into the next child path component if the 
      * match is on a directory. 
      */ 
      if (entryFilename.equalsIgnoreCase(childComponent)) { 
       if (offset == relativeChild.getNameCount() - 1 && Files.isRegularFile(entry)) { 
        return entry; 
       } 
       else if (Files.isDirectory(entry)) { 
        return buildPath(entry, relativeChild, offset + 1); 
       } 
      } 
     } 
    } 

    // No matches found; path can't exist 
    return null; 
} 
0

至于问题的第一部分:使用Path.toRealPath。它不仅处理区分大小写,还包括符号链接(取决于您给出的参数选项)等。这需要Java 7或更高版本。

至于问题的第二部分:不确定你的意思是'处理'。

+0

Path.toRealPath对我来说非常适合将文件转换为其真实表示。 – Arne 2017-08-15 08:05:46

+0

适用于Windows,但仍然在Linux上失败。 – Matthieu 2017-08-28 13:48:09

0

您可以使用此代码执行操作。 由于规范文件名称返回文件的名称(区分大小写),如果得到的内容不等于,文件将以相同的名称存在,但存在不同的大小写。

在Windows上,如果文件存在,无论如何,它将返回true。如果该文件不存在,则规范名称将相同,因此它将返回false。

在Linux上,如果文件存在不同的大小写,它将返回这个不同的名称,并且该方法将返回true。如果它存在相同的情况,则第一个测试返回true。

在这两种情况下,如果文件不存在且名称和规范名称相同,则文件确实不存在。

public static boolean fileExistsCaseInsensitive(String path) { 
    try { 
     File file = new File(path); 
     return file.exists() || !file.getCanonicalFile().getName().equals(file.getName()); 
    } catch (IOException e) { 
     return false; 
    } 
}