2009-08-19 6 views
13

파일 경로가 String 형식입니다. Java에서 파일 시스템에 해당 파일이 있는지 확인해야합니다 (코드는 Windows, Linux 및 OS X에서 실행될 때 교차 플랫폼이어야합니다).대소 문자를 구별하지 않는 File.equals 대소 문자를 구분하는 파일 시스템

문제는 파일 경로와 파일 자체의 경우가 동일한 파일을 나타내지 만 일치하지 않을 수 있습니다 (이는 아마도 Windows에서 시작 되었기 때문에 불일치가 발견되지 않았기 때문일 수 있습니다).

예를 들어 파일 경로가 "ABC.txt"입니다. "abc.txt"라는 파일이 파일 시스템에 존재합니다. 다음 코드는 리눅스에 사실 Windows에서하지만 거짓를 반환합니다

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

무엇 파일이 존재하는지 확인하는 가장 좋은 방법이며, 파일의 파일에 대한 핸들을 반환 존재하는 경우 체계?

답변

13

디렉토리 (File.list())에서 파일 목록을 가져오고 equalsIgnoreCase()을 사용하여 이름을 비교하십시오.

+0

네, 그 가능성은 있지만 경로의 디렉토리가 잘못된 경우에도 문제가 계속 존재합니다. 솔루션은 대소 문자를 구분하지 않고 검색하는 디렉토리 트리를 따라가는 일종의 재귀 알고리즘이 될 수 있습니다. 하지만 더 나은 솔루션을 찾고 있습니다! – jwaddell

+1

@jwaddell : 파일 이름/경로가 어떤 케이스 에나있을 수 있고 리눅스 OS가 대소 문자를 구분하여 처리하기 때문에 더 나은 해결책이 없다고 생각합니다. –

+0

마지 못해이 솔루션과 재귀 적 경로 검사를 구현할 것 같습니다. – jwaddell

1

불일치가 무작위이면 재귀 경로 세그먼트 검사를 포함한 Shimi의 솔루션이 가장 좋습니다. 언뜻보기에는보기가 들리지 않지만 별개의 클래스에서이 마법을 숨기고 간단한 API를 구현하여 주어진 파일 이름에 대한 파일 핸들을 반환하므로 Translator.translate(file) 호출과 같은 것을 볼 수 있습니다.

불일치는 일종의 정적이며 예측 가능합니다. 그렇다면 주어진 파일 이름을 Windows/Linux 파일 이름으로 변환하는 데 사용할 수있는 사전을 선호합니다. 이것은 다른 방법보다 큰 장점이 있습니다 : 잘못된 파일 핸들을 얻는 위험은 더 적습니다.

사전이 실제로 정적 인 경우 속성 파일을 만들고 유지 관리 할 수 ​​있습니다. 정적이지만 더 복잡한 경우 주어진 파일 이름이 하나 이상의 가능한 대상 파일 이름으로 변환 될 수 있다고 가정하면 Map<String, Set<String>> 데이터 구조 (Set은 중복 된 대체 문자가 없으므로 List보다 우선 함)로 dictonary 클래스를 백업합니다.

+0

불행히도 파일 이름은 무엇이든 될 수 있습니다. – jwaddell

2

jwaddell이 말했듯이, VERY SLOW 재귀 경로 검사가 (분명히) 유일한 방법입니다. 다음은 파일 경로 인 String을 받아들이는 Java로 작성된 제 함수입니다. filepath의 문자열 표현이 존재하고 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

f.exists()가 false를 반환하므로이 예제에서는 Linux에서도 false를 반환합니다. – jwaddell

+3

+1 주제 질문에 대한 답변이 정확하지 않지만 Windows에서 대소 문자를 구분하는 검사를 수행하는 데 도움이되었습니다. – Dmitry

1

다음은 내 경로가 알려져 있고 상대 경로가 디스크의 경로와 다른 경우가있는 상황을위한 Java 7 솔루션입니다.예를 들어

파일 /tmp/foo/biscuits 주어진 방법이 제대로 다음 입력을 파일에 Path를 반환 :

  • /tmpfoo/biscuits
  • /tmpfoo/BISCUITS
  • /tmpFOO/BISCUITS
  • /tmpFOO/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

이 코드를 사용하여 원하는 것을 할 수 있습니다. 정식 파일 이름은 대/소문자를 구분하여 파일 이름을 반환하므로 같지 않은 파일이 있으면 이름은 같지만 대/소문자가 다릅니다.

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; 
    } 
} 
관련 문제