java 不区分大小写的 File.equals 在区分大小写的文件系统上

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1297879/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 16:00:37  来源:igfitidea点击:

Case-insensitive File.equals on case-sensitive file system

javafilecross-platformfilesystemscase-sensitive

提问by jwaddell

I have a file path in String form. In Java, I need to determine if that file exists on the file system (and our code needs to be cross-platform as it runs on Windows, Linux and OS X).

我有一个字符串形式的文件路径。在 Java 中,我需要确定文件系统中是否存在该文件(并且我们的代码需要跨平台,因为它在 Windows、Linux 和 OS X 上运行)。

The problem is that the case of the file path and the file itself may not match, even though they do represent the same file (presumably this is because they originated on Windows and the discrepancy was not noticed).

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

For example, I have a file path of "ABC.txt". A file called "abc.txt" exists on the file system. The following code will return trueon Windows but falseon Linux:

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

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

What is the best way to determine if the file exists, and if it exists to return a handle to the file on the file system?

确定文件是否存在以及是否存在以返回文件系统上文件的句柄的最佳方法是什么?

采纳答案by Shimi Bandiel

Get the list of files from the directory (File.list()) and compare the names using equalsIgnoreCase().

从目录 ( File.list()) 中获取文件列表并使用equalsIgnoreCase().

回答by Lenny T

This method will tell you if a file with the exact name in question exists (the path portion is not case sensitive).

此方法将告诉您是否存在具有相关确切名称的文件(路径部分不区分大小写)。

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

回答by Eric Leschinski

As jwaddell said, it looks like the VERY SLOW recursive path checking is (apparently) the only way to do this. Here is my function written in java that accepts a String which is a filepath. If the string representation of the filepath exists and has identical case sensitivity to the one reported by windows, then it returns true, else false.

正如 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;

}

回答by Andreas Dolk

If the discrepancies are random, then to me Shimi's solution including recursive path segment checking is the best solution. It sounds ugly at a first glance but you can hide the magic in a separate class and implement a simple API to return the file handle for a given file name, so you just see something like a Translator.translate(file)call.

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

Maybe, the discrepancies are kind of static, predictable. Then I would prefer a dictionary that can be used to translate a given file name to Windows/Linux file names. This has on big advantage over the other method: the risk to get a wrong file handle is smaller.

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

If the dictionary was really static, you could create and maintain a properties file. If it was static but more complex, say a given file name could be translated to more than one possible target file names, I'd back up the dictonary class with a Map<String, Set<String>>datastructure (Setpreferred over Listbecause there are no duplicate alternates).

如果字典真的是静态的,您可以创建和维护一个属性文件。如果它是静态的但更复杂,比如说给定的文件名可以转换为多个可能的目标文件名,我会用数据Map<String, Set<String>>结构备份字典类(Set首选,List因为没有重复的替代品)。

回答by nullPainter

Here's my Java 7 solution, for situations where a parent path is known and a relative child path may have different case to the path on disk.

这是我的 Java 7 解决方案,适用于已知父路径并且相对子路径可能与磁盘上的路径具有不同大小写的情况。

For example, given the file /tmp/foo/biscuits, the method will correctly return a Pathto the file with the following input:

例如,给定文件/tmp/foo/biscuits,该方法将Path使用以下输入正确返回文件:

  • /tmpand foo/biscuits
  • /tmpand foo/BISCUITS
  • /tmpand FOO/BISCUITS
  • /tmpand FOO/biscuits
  • /tmpfoo/biscuits
  • /tmpfoo/BISCUITS
  • /tmpFOO/BISCUITS
  • /tmpFOO/biscuits

Note that this solution has notbeen robustly tested so should be considered a starting point rather than a production-ready snippet.

请注意,此解决方案尚未经过稳健测试,因此应将其视为起点,而不是可用于生产的片段。

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

回答by Dennis Hendriks

As for the first part of the question: use Path.toRealPath. It not only handles case sensitivity, but also symbolic links (depending on the options you give as parameters), etc. This requires Java 7 or above.

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

As for the second part of the question: not sure what you mean with 'handle'.

至于问题的第二部分:不确定“句柄”是什么意思。

回答by Julien Feniou

You can do what you are looking for with this code. Since the Canonical File name returns the name of the file, case sensitive, if you get something not equals, the file exists with the same name but a different case.

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

On Windows, if the file exists, with any case, it will return true. If the file doesn't exists, the canonical name will be the same, so it will return false.

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

On Linux, if the file exists with a different case, it will return this different name, and the method will return true. If it exists with same case, the first test return true.

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

In both case, if the file doesn't exist and the name and canonical name are the same, the file really doesn't exist.

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

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

回答by MariuszS

File file = newCaseInsensitiveFile("ABC.txt");

Implementation:

执行:

private static File newCaseInsensitiveFile(String ignoreCaseFilename) {
    try {
        return Files.list(new File(".").getAbsoluteFile().toPath().getParent())
            .filter(file -> file.getFileName().toString().equalsIgnoreCase(ignoreCaseFilename))
            .map(Path::toFile)
            .findFirst()
            .orElse(new File(ignoreCaseFilename));
    } catch (IOException e) {
        return new File(ignoreCaseFilename);
    }
}

Note:this only works from currentdirectory (".").

注意:这只适用于当前目录(“.”)。