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


77

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

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

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

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


klocwork的相关检查工具文档:SV.PATH:包含有用的指南
boly38 '02

Answers:


39

这也将检查目录的存在。

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

似乎file.canWrite()并没有明确指示您是否具有写入目录的权限。


16
嗯...我不想检查文件是否存在,我想检查文件是否可以在当前文件系统上创建
Raibaz

问题在于,检查文件是否可以创建之后,创建文件的能力可能会发生变化
tddmonkey

3
krosenvold的代码执行以下操作:传入的文件名是否已作为目录存在,如果不存在,该目录将存在吗?这是有道理的,因为如果目录存在,您可以创建文件(允许权限)。请注意,新File(“ foo”)不会创建文件。
mtruesdell,2009年

将其放入文件“ c:[] [] cygwin \\ cygwin.bat”的构造函数中...然后将打印语句放入两个ifs中-第一个if中为“ dir found”,第二个if中为“ file found”。找到dir的输出,这显然与检查路径名是否有效/合法不同。
大卫·布莱恩

3
备注:您实际上并没有使用在文件系统上创建文件new File("some/path")。因此,您可以将它们用于验证目的。要创建文件系统上的文件将不得不createNewFile等等
麦克

38

在Java 7中引入了Path类增加了新的选择,如下所示:
(不不是下正常工作的Linux -总是返回true)

/**
 * <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;
}

美丽!美观而干净的解决方案,无需包含Apache库。
蒂姆·维西(TimVisée)2016年

谢谢@ TimVisée。我很高兴您找到一个好的解决方案(0:
c0der

请注意,这总是true在linux上返回。在我自己的测试中,甚至Paths.get("file\u0000")没有抛出InvalidPathException
Groostav

2
@nllsdfx-除了不能在Linux上使用外,为此返回true怎么了?如果目录名称中带有句点,那是完全有效的,如果那是您认为不对的…
ArtOfWarfare

1
在Linux上,文件名中唯一不允许使用的字符是NUL字符,该字符在Linux下也有效
Ferrybig

17

File.getCanonicalPath()为此非常有用。IO抛出异常某些类型的无效文件名(例如CONPRN*?*在Windows中)针对OS或文件系统解析时。但是,这仅是初步检查;在实际创建文件时,您仍然需要处理其他失败(例如,权限不足,驱动器空间不足,安全限制)。


AFAICT,尽管有些外壳环境可能会抱怨,但没有什么可以阻止以编程方式创建名为CON的Windows 10文件。也许这仅指的是CMD.exe(可能还有其他)如何解释CON(和其他字符串)。
philwalk

15

尝试创建文件时,很多事情都会出错:

  • 您缺少必要的权限;
  • 设备上没有足够的空间。
  • 设备遇到错误;
  • 某些自定义安全性策略禁止您创建特定类型的文件。
  • 等等

更重要的是,这些内容可能会在您尝试查询是否可行以及何时实际可行之间进行更改。在多线程环境中,这是争用条件的主要原因之一,并且可能是某些程序的真正漏洞。

基本上,您只需要尝试创建它,看看它是否有效。这是正确的方法。这就是为什么诸如此类的东西ConcurrentHashMap具有一个putIfAbsent()check和insert是一个原子操作并且不受竞争条件影响的原因。完全相同的原理在这里起作用。

如果这只是某些诊断或安装过程的一部分,请执行此操作并查看其是否有效。同样,不能保证它将在以后工作。

基本上,您的程序必须足够强大,才能在无法编写相关文件的情况下正常退出。


3
不要“死”(在某些情况下),允许用户选择另一个卷或媒体。我有一个IDE,当它无法写入其项目文件时,它会死。该卷处于脱机状态-请允许我选择另一个位置并继续。
吉姆·

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

5
由于File.canWrite()在Windows上不可靠,因此无法完全正常工作。有关变通方法,请参见此帖子和Peter Tseng的第一条评论。
沙利文

1

您可以做一些可以跨操作系统使用的操作

使用正则表达式匹配来检查现有的已知无效字符。

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

优点

  • 这适用于所有操作系统
  • 您可以通过编辑该正则表达式以任何方式自定义它。

缺点

  • 这可能不是一个完整的列表,需要更多的研究来填写更多无效的模式或字符。

0

随便做(然后自己清理)

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

也许这是最可靠的方法。

以下是canCreateOrIsWritable确定您的程序是否能够创建文件及其父目录的信息在给定路径下,或者,如果那里已有文件,则对其进行写入。

通过实际创建必要的父目录以及路径中的空文件来实现。之后,它将删除它们(如果路径中存在文件,则将其保留)。

使用方法如下:

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

基本方法和一些辅助方法:

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

请记住,在调用canCreateOrIsWritable和创建实际文件之间,文件系统的内容和权限可能已更改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.