简体   繁体   English

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

[英]Case-insensitive File.equals on case-sensitive file system

I have a file path in String form. 我有一个String形式的文件路径。 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". 例如,我的文件路径为“ABC.txt”。 A file called "abc.txt" exists on the file system. 文件系统上存在名为“abc.txt”的文件。 The following code will return true on Windows but false on Linux: 以下代码将在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? 确定文件是否存在以及是否存在返回文件系统上文件句柄的最佳方法是什么?

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

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

As jwaddell said, it looks like the VERY SLOW recursive path checking is (apparently) the only way to do this. 正如jwaddell所说,看起来非常缓慢的递归路径检查(显然)是唯一的方法。 Here is my function written in java that accepts a String which is a filepath. 这是我用java编写的函数,它接受一个String文件路径。 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. 如果文件路径的字符串表示存在且与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;

}

If the discrepancies are random, then to me Shimi's solution including recursive path segment checking is the best solution. 如果差异是随机的,那么对我来说,Shimi的解决方案包括递归路径段检查是最好的解决方案。 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. 乍一看听起来很难看,但你可以在一个单独的类中隐藏魔法并实现一个简单的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. 然后我更喜欢可用于将给定文件名转换为Windows / Linux文件名的字典。 This has on big advantage over the other method: the risk to get a wrong file handle is smaller. 与其他方法相比,这具有很大的优势:获取错误文件句柄的风险较小。

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 ( Set preferred over List because there are no duplicate alternates). 如果它是静态的但更复杂,比如给定的文件名可以转换为多个可能的目标文件名,我将使用Map<String, Set<String>>结构备份dictonary类(在List Set首选因为没有重复的替代品)。

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 Path to the file with the following input: 例如,给定文件/tmp/foo/biscuits ,该方法将使用以下输入正确返回文件的Path

  • /tmp and foo/biscuits /tmpfoo/biscuits
  • /tmp and foo/BISCUITS /tmpfoo/BISCUITS
  • /tmp and FOO/BISCUITS /tmpFOO/BISCUITS
  • /tmp and FOO/biscuits /tmpFOO/biscuits

Note that this solution has not been 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;
}

As for the first part of the question: use Path.toRealPath . 至于问题的第一部分:使用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. 它不仅处理区分大小写,还处理符号链接(取决于您作为参数提供的选项)等。这需要Java 7或更高版本。

As for the second part of the question: not sure what you mean with 'handle'. 至于问题的第二部分:不确定你对'句柄'的意思。

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. 由于Canonical File名称返回文件的名称,区分大小写,如果得到的内容不相等,则该文件存在同名但具有不同的大小写。

On Windows, if the file exists, with any case, it will return true. 在Windows上,如果文件存在,则无论如何都会返回true。 If the file doesn't exists, the canonical name will be the same, so it will return false. 如果该文件不存在,则规范名称将相同,因此它将返回false。

On Linux, if the file exists with a different case, it will return this different name, and the method will return true. 在Linux上,如果文件存在不同的大小写,它将返回此不同的名称,该方法将返回true。 If it exists with same case, the first test return 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;
    }
}
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 current directory ("."). 注意:这仅适用于当前目录(“。”)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 获取文件区分大小写的名称,不区分大小写拼写 - Get file case-sensitive name, with case-insensitive spelling 如何确保区分大小写的资源是我在不区分大小写的文件系统上寻找的资源? - How to ensure case-sensitive resource is what I look for on a case-insensitive file system? 当java(解释器)区分大小写时,为什么Java编译器(javac)不区分大小写? - Why is the Java compiler (javac) case-insensitive when java (the interpreter) is case-sensitive? 如何检测文件系统是否区分大小写? - How do I detect whether the file system is case-sensitive? 区分大小写的文件名处理 - case-sensitive processing of file names 不区分大小写的等于使用 Hibernate 标准 - Case-insensitive equals using Hibernate Criteria 将Java应用程序从不区分大小写的文件系统移动到区分大小写的文件系统 - Moving java app from a case insensitive file system to a case sensitive one 如何在 Linux 系统上按字典顺序(不区分大小写)对文件\目录树进行排序 - How to sort file\directory tree in lexicographic order (case-insensitive) on Linux system javap命令不区分大小写吗? - Is command javap not case-sensitive? 不区分大小写的搜索和替换 - Case-insensitive search and replace
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM