2009-08-19 1 views
13

У меня есть путь к файлу в строковой форме. В Java мне нужно определить, существует ли этот файл в файловой системе (и наш код должен быть кросс-платформенным, поскольку он работает в Windows, Linux и OS X).Нечувствительность к регистру File.equals в зависящей от регистратора файловой системе

Проблема в том, что случай пути к файлу и самого файла может не совпадать, даже если они представляют один и тот же файл (предположительно это происходит из-за того, что они возникли из Windows, и это несоответствие не было замечено).

Например, у меня есть путь к файлу «ABC.txt». В файловой системе существует файл с именем «abc.txt». Следующий код будет возвращать истинное на Windows, но ложные на Linux:

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

Каков наилучший способ определить, если файл существует, и если он существует, чтобы вернуть дескриптор файла на файл система?

ответ

13

Получить список файлов из каталога (File.list()) и сравнить имена с помощью equalsIgnoreCase().

+0

Да, это возможно, но проблема все еще существует, если каталоги на пути являются неправильным случаем. Решение может оказаться своего рода рекурсивным алгоритмом, который поднимается в дереве каталогов, не просматривая без учета регистра. Но я надеюсь на лучшее решение! – jwaddell

+1

@jwaddell: я не думаю, что есть лучшее решение, так как имя файла/путь может находиться в любом корпусе, а linux os рассматривает его в режиме, чувствительном к регистру. –

+0

Похоже, я неохотно реализую это решение, а также рекурсивную проверку пути. – jwaddell

1

Если несоответствия случайны, то для меня лучшим решением является решение Shimi, включающее рекурсивную проверку сегмента пути. Это звучит уродливо с первого взгляда, но вы можете скрыть магию в отдельном классе и реализовать простой API, чтобы вернуть дескриптор файла для заданного имени файла, поэтому вы просто видите что-то вроде звонка Translator.translate(file).

Возможно, расхождения являются своего рода статическими, предсказуемыми. Тогда я бы предпочел словарь, который можно использовать для перевода имени файла в имена файлов Windows/Linux. Это имеет большое преимущество перед другим методом: риск получить неправильный дескриптор файла меньше.

Если словарь действительно статичен, вы можете создать и сохранить файл свойств. Если он был статичным, но более сложным, скажем, что указанное имя файла может быть переведено на несколько возможных имен целевых файлов, я бы создал резервную копию класса диктатуры с помощью структуры данных Map<String, Set<String>> (Set, предпочитаемой более List, потому что нет повторяющихся альтернатив).

+0

К сожалению, имена файлов могут быть любыми. – jwaddell

2

Как говорит jwaddell, похоже, что проверка рекурсивного пути ОЧЕНЬ МЕДЛЕННО является (по-видимому) единственным способом сделать это. Вот моя функция, написанная в java, которая принимает String, которая является файловой дорожкой. Если строковое представление пути к файлу существует и имеет идентичную чувствительность к регистру, о которой сообщают окна, тогда он возвращает true, else 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

Это все равно вернет false для Linux для моего примера, так как f.exists() вернет false. – jwaddell

+3

+1 Не совсем ответ на вопрос по теме, но помог мне выполнить проверку с учетом регистра в Windows. – Dmitry

1

Это мое решение Java 7, в ситуациях, когда родительский путь известен и относительный путь к потомку может иметь различный случай для пути на диске.

Например, если файл /tmp/foo/biscuits, метод будет корректно возвращать Path в файл со следующим входом:

  • /tmp и foo/biscuits
  • /tmp и foo/BISCUITS
  • /tmp и FOO/BISCUITS
  • /tmp и 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

+0

Хорошо для Windows, но все равно не работает на Linux. – Matthieu

0

Вы можете делать то, что ищете с помощью этого кода. Поскольку имя Canonical File возвращает имя файла с учетом регистра, если вы получаете что-то не равное, файл существует с тем же именем, но в другом случае.

В 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; 
    } 
} 
Смежные вопросы