2009-08-19 5 views
13

Mam ścieżkę pliku w formie ciągu. W Javie muszę ustalić, czy ten plik istnieje w systemie plików (a nasz kod musi być wieloplatformowy, ponieważ działa w systemach Windows, Linux i OS X).Nie jest rozróżniana wielkość liter pliku.equals w systemie plików z rozróżnianiem wielkości liter

Problem polega na tym, że przypadek ścieżki pliku i samego pliku może się nie zgadzać, mimo że reprezentują ten sam plik (prawdopodobnie dlatego, że pochodziły z systemu Windows, a rozbieżności nie zostały zauważone).

Na przykład mam ścieżkę pliku "ABC.txt". Plik o nazwie "abc.txt" istnieje w systemie plików. Poniższy kod powróci prawdziwą w systemie Windows, ale fałszywy w systemie Linux:

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

Jaki jest najlepszy sposób, aby określić, czy plik istnieje, a jeśli istnieje, to zwraca uchwyt do pliku na plik system?

Odpowiedz

13

Pobierz listę plików z katalogu (File.list()) i porównaj nazwy za pomocą equalsIgnoreCase().

+0

Tak, jest to możliwe, ale problem nadal występuje, jeśli katalogi na ścieżce są nieprawidłowe. Rozwiązaniem może być algorytm rekursywny, który idzie w górę drzewa katalogów bez rozróżniania wielkości liter. Ale mam nadzieję na lepsze rozwiązanie! – jwaddell

+1

@jwaddell: Nie sądzę, że istnieje lepsze rozwiązanie, ponieważ nazwa pliku/ścieżka może znajdować się w dowolnej obudowie, a system Linux traktuje go w trybie rozróżniania wielkości liter. –

+0

Wygląda na to, że niechętnie zaimplementuję to rozwiązanie plus rekurencyjne sprawdzanie ścieżki. – jwaddell

1

Jeśli rozbieżności są przypadkowe, to dla mnie rozwiązaniem Shimi'ego, w tym rekurencyjnym segmentowaniem ścieżki, jest najlepsze rozwiązanie. Na pierwszy rzut oka brzmi to brzydko, ale możesz ukryć magię w oddzielnej klasie i zaimplementować prosty interfejs API, aby zwrócić uchwyt pliku dla danej nazwy pliku, więc zobaczysz coś takiego jak wywołanie Translator.translate(file).

Być może rozbieżności są dość statyczne, przewidywalne. Wtedy wolałbym słownik, który może być użyty do przetłumaczenia danej nazwy pliku na nazwy plików systemu Windows/Linux. Ma to dużą zaletę w porównaniu z inną metodą: ryzyko uzyskania niewłaściwego uchwytu pliku jest mniejsze.

Jeśli słownik był naprawdę statyczny, można utworzyć i zachować plik właściwości. Jeśli byłby statyczny, ale bardziej złożony, powiedzmy, że nazwa pliku mogłaby zostać przetłumaczona na więcej niż jedną możliwą nazwę pliku docelowego, zarchiwizowałbym klasę dictonary z datastructure Map<String, Set<String>> (Set preferowana ponad List, ponieważ nie ma duplikatów alternatywnych).

+0

Niestety nazwy plików mogą być dowolne. – jwaddell

2

Jak powiedział jwaddell, wygląda na to, że BARDZO SLOWNA kontrola ścieżki rekursywnej jest (najwyraźniej) jedynym sposobem, aby to zrobić. Oto moja funkcja napisana w języku Java, która akceptuje łańcuch znaków, który jest ścieżką do pliku. Jeśli ciąg znaków reprezentujący ścieżkę do pliku istnieje i ma identyczną wielkość liter do raportu zgłoszonego przez system Windows, to zwraca wartość true, w przeciwnym wypadku 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

Metoda ta powie Ci, czy plik z dokładną nazwą w pytaniu istnieje (część ścieżka nie jest uwzględniana wielkość liter).

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

To będzie nadal zwracać fałsz na Linuksie na mój przykład, ponieważ f.exists() zwróci false. – jwaddell

+3

+1 Niezupełnie odpowiedź na pytanie na temat, ale pomogła mi wykonać sprawdzenie wielkości liter w systemie Windows. – Dmitry

1

Oto moje rozwiązanie Java 7, w sytuacji, gdy rodzic jest ścieżka znanych i ścieżką względną dziecko może mieć inną sprawę do ścieżki na dysku.

Przykładowo, jeżeli plik /tmp/foo/biscuits, sposób poprawnie zwrócić Path do pliku z następującym wejścia:

  • /tmp i foo/biscuits
  • /tmp i foo/BISCUITS
  • /tmp i FOO/BISCUITS
  • /tmp i FOO/biscuits

Należy zauważyć, że to rozwiązanie ma , a nie zostało przetestowane, więc powinno być traktowane jako punkt wyjścia, a nie gotowy do produkcji fragment.

/** 
* 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

Co do pierwszej części pytania: użyj Path.toRealPath. Obsługuje nie tylko rozróżnianie wielkości liter, ale także dowiązania symboliczne (w zależności od opcji podanych jako parametry) itp. Wymaga to języka Java 7 lub nowszego.

Co do drugiej części pytania: nie jestem pewien, co masz na myśli mówiąc "uchwyt".

+0

Path.toRealPath działa dobrze dla mnie, aby przekonwertować plik na jego rzeczywistą reprezentację. – Arne

+0

Dobry dla systemu Windows, ale nadal nie działa na Linuksie. – Matthieu

0

Możesz zrobić to, czego szukasz z tym kodem. Ponieważ nazwa pliku kanonicznego zwraca nazwę pliku, wielkość liter jest rozróżniana, jeśli coś jest nie równe, plik istnieje pod tą samą nazwą, ale w innym przypadku.

W systemie Windows, jeśli plik istnieje, w każdym przypadku zwróci true. Jeśli plik nie istnieje, nazwa kanoniczna będzie taka sama, więc zwróci false.

W systemie Linux, jeśli plik istnieje w innym przypadku, zwróci tę inną nazwę, a metoda zwróci wartość true. Jeśli istnieje w tym samym przypadku, pierwszy test zwraca true.

W obu przypadkach, jeśli plik nie istnieje, a nazwa i nazwa kanoniczna są takie same, plik naprawdę nie istnieje.

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; 
    } 
} 
Powiązane problemy