Файл без учета регистра.равно в файловой системе с учетом регистра

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

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

например, у меня есть путь к файлу " ABC.формат txt." Ля файл под названием "Азбука.txt " существует в файловой системе. Следующий код будет возвращать правда на Windows, но false на Linux:

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

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

7 ответов


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


этот метод покажет вам, Существует ли файл с точным именем (часть пути не чувствительна к регистру).

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

Как сказал jwaddell, похоже, что очень медленная рекурсивная проверка пути (по-видимому) - единственный способ сделать это. Вот моя функция, написанная на java, которая принимает строку, которая является filepath. Если строковое представление пути к файлу существует и имеет одинаковую чувствительность к регистру для Windows, то оно возвращает 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;

}

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

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

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


вот мое решение 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;
}

Что касается первой части вопроса: используйте путь.toRealPath. Он не только обрабатывает чувствительность к регистру, но и символические ссылки (в зависимости от параметров, которые вы даете в качестве параметров) и т. д. Для этого требуется Java 7 или выше.

Что касается второй части вопроса: не уверен, что вы имеете в виду с "ручкой".


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

в 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;
    }
}