Файл без учета регистра.равно в файловой системе с учетом регистра
У меня есть путь к файлу в виде строки. В 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;
}
}