Ответ 1
Получить список файлов из каталога (File.list()
) и сравнить имена с помощью equalsIgnoreCase()
.
У меня есть путь к файлу в строковой форме. В Java мне нужно определить, существует ли этот файл в файловой системе (и наш код должен быть кросс-платформенным, поскольку он работает в Windows, Linux и OS X).
Проблема в том, что случай пути к файлу и самого файла может не совпадать, даже если они представляют один и тот же файл (предположительно это происходит из-за того, что они возникли из Windows, и это несоответствие не было замечено).
Например, у меня есть путь к файлу "ABC.txt". В файловой системе существует файл с именем "abc.txt". Следующий код вернет true в Windows, но false в Linux:
new File("ABC.txt").exists();
Каков наилучший способ определить, существует ли файл, и существует ли он, чтобы вернуть дескриптор файла в файловой системе?
Получить список файлов из каталога (File.list()
) и сравнить имена с помощью equalsIgnoreCase()
.
Этот метод скажет вам, существует ли файл с точным именем (часть пути не чувствительна к регистру).
public static boolean caseSensitiveFileExists(String pathInQuestion) {
File f = new File(pathInQuestion);
return f.exists() && f.getCanonicalPath().endsWith(f.getName());
}
Как сказал jwaddell, похоже, что проверка рекурсивного пути VERY SLOW является (по-видимому) единственным способом сделать это. Вот моя функция, написанная в java, которая принимает String, которая является файловой дорожкой. Если строковое представление пути к файлу существует и имеет идентичную чувствительность к регистру, о которой сообщают окна, тогда он возвращает 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. Это имеет большое преимущество по сравнению с другим методом: риск получить неправильный дескриптор файла меньше.
Если словарь действительно статичен, вы можете создать и сохранить файл свойств. Если бы он был статичным, но более сложным, скажем, что данное имя файла может быть переведено на несколько возможных имен целевых файлов, я бы поддержал класс-диктар с 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;
}
Что касается первой части вопроса: используйте Path.toRealPath. Он не только обрабатывает чувствительность к регистру, но и символические ссылки (в зависимости от параметров, которые вы указываете как параметры) и т.д. Для этого требуется Java 7 или выше.
Что касается второй части вопроса: не уверен, что вы имеете в виду с "handle".
Вы можете делать то, что ищете, с помощью этого кода. Поскольку имя 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;
}
}