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

问题描述 投票:14回答:8

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

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

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

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

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

java file cross-platform filesystems case-sensitive
8个回答
14
投票

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


6
投票

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

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

2
投票

正如jwaddell所说,看起来非常缓慢的递归路径检查(显然)是唯一的方法。这是我用java编写的函数,它接受一个String文件路径。如果文件路径的字符串表示存在且与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;

}

1
投票

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

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

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


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
投票

您可以使用此代码执行您要查找的内容。由于Canonical File名称返回文件的名称,区分大小写,如果得到的内容不相等,则该文件存在同名但具有不同的大小写。

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

0
投票
File file = newCaseInsensitiveFile("ABC.txt");

执行:

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

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

© www.soinside.com 2019 - 2024. All rights reserved.