在 Java 中有没有一种方法可以在不尝试创建文件的情况下确定路径是否有效?

我需要确定用户提供的字符串是否是一个有效的文件路径(即,如果 createNewFile()将成功或抛出一个异常) ,但我不想用无用的文件膨胀文件系统,创建只是为了验证目的。

有没有一种方法可以确定我拥有的字符串是否是一个有效的文件路径,而无需尝试创建文件?

我知道“有效文件路径”的定义因操作系统而异,但我想知道是否有任何快速接受 C:/foo/foo并拒绝 banana的方法。

一种可能的方法是尝试创建文件,如果创建成功,最终将其删除,但我希望有一种更优雅的方法来实现相同的结果。

116328 次浏览

This would check for the existance of the directory as well.

File file = new File("c:\\cygwin\\cygwin.bat");
if (!file.isDirectory())
file = file.getParentFile();
if (file.exists()){
...
}

It seems like file.canWrite() does not give you a clear indication if you have permissions to write to the directory.

A number of things can go wrong when you try and create a file:

  • Your lack the requisite permissions;
  • There is not enough space on the device;
  • The device experiences an error;
  • Some policy of custom security prohibits you from creating a file of a particular type;
  • etc.

More to the point, those can change between when you try and query to see if you can and when you actually can. In a multithreaded environment this is one of the primary causes of race conditions and can be a real vulnerability of some programs.

Basically you just have to try and create it and see if it works. And that's the correct way to do it. It's why things like ConcurrentHashMap has a putIfAbsent() so the check and insert is an atomic operation and doesn't suffer from race conditions. Exactly the same principle is in play here.

If this is just part of some diagnostic or install process, just do it and see if it works. Again there's no guarantee that it'll work later however.

Basically your program has to be robust enough to die gracefully if it can't write a relevant file.

File.getCanonicalPath() is quite useful for this purpose. IO exceptions are thrown for certain types of invalid filenames (e.g. CON, PRN, *?* in Windows) when resolving against the OS or file system. However, this only serves as a preliminary check; you will still need to handle other failures when actually creating the file (e.g. insufficient permissions, lack of drive space, security restrictions).

boolean canWrite(File file) {
if (file.exists()) {
return file.canWrite();
}
else {
try {
file.createNewFile();
file.delete();
return true;
}
catch (Exception e) {
return false;
}
}
}

Path class introduced in Java 7 adds new alternatives, like the following:

/**
* <pre>
* Checks if a string is a valid path.
* Null safe.
*
* Calling examples:
*    isValidPath("c:/test");      //returns true
*    isValidPath("c:/te:t");      //returns false
*    isValidPath("c:/te?t");      //returns false
*    isValidPath("c/te*t");       //returns false
*    isValidPath("good.txt");     //returns true
*    isValidPath("not|good.txt"); //returns false
*    isValidPath("not:good.txt"); //returns false
* </pre>
*/
public static boolean isValidPath(String path) {
try {
Paths.get(path);
} catch (InvalidPathException | NullPointerException ex) {
return false;
}
return true;
}

Edit:
Note Ferrybig's comment : "The only disallowed character in a file name on Linux is the NUL character, this does work under Linux."

Here's something you can do that works across operating systems

Using regex match to check for existing known invalid characters.

if (newName.matches(".*[/\n\r\t\0\f`?*\\<>|\":].*")) {
System.out.println("Invalid!");
} else {
System.out.println("Valid!");
}

Pros

  • This works across operating systems
  • You can customize it whatever way you want by editing that regex.

Cons

  • This might not be a complete list and need more research to fill in more invalid patterns or characters.

Just do it (and clean up after yourself)

A possible approach may be attempting to create the file and eventually deleting it if the creation succeeded, but I hope there is a more elegant way of achieving the same result.

Maybe that's the most robust way.

Below is canCreateOrIsWritable that determines whether your program is able to create a file and its parent directories at a given path, or, if there's already a file there, write to it.

It does so by actually creating the necessary parent directories as well as an empty file at the path. Afterwards, it deletes them (if there existed a file at the path, it's left alone).

Here's how you might use it:

var myFile = new File("/home/me/maybe/write/here.log")


if (canCreateOrIsWritable(myFile)) {
// We're good. Create the file or append to it
createParents(myFile);
appendOrCreate(myFile, "new content");
} else {
// Let's pick another destination. Maybe the OS's temporary directory:
var tempDir = System.getProperty("java.io.tmpdir");
var alternative = Paths.get(tempDir, "second_choice.log");
appendOrCreate(alternative, "new content in temporary directory");
}

The essential method with a few helper methods:

static boolean canCreateOrIsWritable(File file) {
boolean canCreateOrIsWritable;


// The non-existent ancestor directories of the file.
// The file's parent directory is first
List<File> parentDirsToCreate = getParentDirsToCreate(file);


// Create the parent directories that don't exist, starting with the one
// highest up in the file system hierarchy (closest to root, farthest
// away from the file)
reverse(parentDirsToCreate).forEach(File::mkdir);


try {
boolean wasCreated = file.createNewFile();
if (wasCreated) {
canCreateOrIsWritable = true;
// Remove the file and its parent dirs that didn't exist before
file.delete();
parentDirsToCreate.forEach(File::delete);
} else {
// There was already a file at the path → Let's see if we can
// write to it
canCreateOrIsWritable = java.nio.file.Files.isWritable(file.toPath());
}
} catch (IOException e) {
// File creation failed
canCreateOrIsWritable = false;
}
return canCreateOrIsWritable;
}


static List<File> getParentDirsToCreate(File file) {
var parentsToCreate = new ArrayList<File>();
File parent = file.getParentFile();
while (parent != null && !parent.exists()) {
parentsToCreate.add(parent);


parent = parent.getParentFile();
}
return parentsToCreate;
}


static <T> List<T> reverse(List<T> input) {
var reversed = new ArrayList<T>();
for (int i = input.size() - 1; i >= 0; i--) {
reversed.add(input.get(i));
}
return reversed;
}


static void createParents(File file) {
File parent = file.getParentFile();
if (parent != null) {
parent.mkdirs();
}
}

Keep in mind that between calling canCreateOrIsWritable and creating the actual file, the contents and permissions of your file system might have changed.