Java中有没有办法在不尝试创建文件的情况下确定路径是不是有效?

Posted

技术标签:

【中文标题】Java中有没有办法在不尝试创建文件的情况下确定路径是不是有效?【英文标题】:Is there a way in Java to determine if a path is valid without attempting to create a file?Java中有没有办法在不尝试创建文件的情况下确定路径是否有效? 【发布时间】:2010-10-02 21:33:52 【问题描述】:

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

有没有办法在不尝试创建文件的情况下确定我拥有的字符串是否是有效的文件路径?

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

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

【问题讨论】:

来自 klocwork 的相关检查器文档:SV.PATH:包含有用的指南 【参考方案1】:

Java 7 中引入的路径类添加了新的替代方案,如下所示:

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

编辑: 注意 Ferrybig 的 comment : "Linux 上文件名中唯一不允许使用的字符是 NUL 字符,这在 Linux 下确实有效。"

【讨论】:

漂亮!无需包含 Apache 库的不错且干净的解决方案。 谢谢@Tim Visée。我很高兴你发现它是一个很好的解决方案(0: 请注意,这在 linux 上总是返回 true。在我自己的测试中,即使Paths.get("file\u0000") 也不会抛出InvalidPathException @nllsdfx - 除了不能在 Linux 上工作之外,它返回 true 有什么问题?目录名称中带有句点是完全有效的,如果您认为这是错误的... Linux 上文件名中唯一不允许使用的字符是 NUL 字符,这在 Linux 下确实有效【参考方案2】:

就这么做(然后自己清理)

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

也许这是最稳健的方式。

下面是canCreateOrIsWritable,它确定您的程序是否能够在给定路径创建文件及其父目录,或者,如果那里已经有文件,则写入文件。 p>

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

以下是您可以使用的方法:

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 和创建实际文件之间,您的文件系统的内容和权限可能已经改变。

【讨论】:

【参考方案3】:

您可以执行跨操作系统的操作

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

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

优点

这适用于操作系统 您可以通过任何方式对其进行自定义 您可以通过编辑该正则表达式来实现。

缺点

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

【讨论】:

【参考方案4】:
boolean canWrite(File file) 
  if (file.exists()) 
    return file.canWrite();
  
  else 
    try 
      file.createNewFile();
      file.delete();
      return true;
    
    catch (Exception e) 
      return false;
    
  

【讨论】:

这并不完全有效,因为File.canWrite() 在 Windows 上不可靠。有关解决方法,请参阅 this post 和 Peter Tseng 的第一条评论。【参考方案5】:

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

【讨论】:

AFAICT,没有什么可以阻止以编程方式创建名为 CON 的 Windows 10 文件,尽管某些 shell 环境可能会抱怨。也许这仅指 CMD.exe(可能还有其他)如何解释 CON(和其他字符串)。【参考方案6】:

这也会检查目录是否存在。

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

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

【讨论】:

嗯...我不想检查文件是否存在,我想检查文件是否可以在当前文件系统上创建 问题在于,在您检查是否可以创建文件后,创建文件的能力可能会发生变化 krosenvold 的代码执行以下操作:传入的文件名是否已经作为目录存在,如果不存在,它所在的目录是否存在?这是有道理的,因为如果目录在那里,您可以创建文件(权限允许)注意 new File("foo") 不会创建文件。 备注:您实际上并没有在new File("some/path") 的文件系统上创建文件。因此,您可以将这些用于验证目的。要在文件系统上创建文件必须createNewFile等。 我不明白 Java 没有本地方法来测试路径有效性(请不要考虑 Java 7 Path 类,它是“最近的”并且在 Linux 下似乎不成功)。这有什么特别的原因吗?哎呀,在过去的几十年里,路径并没有发生太大变化。【参考方案7】:

尝试创建文件时可能会出现很多问题:

您缺乏必要的权限; 设备空间不足; 设备遇到错误; 某些自定义安全策略禁止您创建特定类型的文件; 等

更重要的是,这些可能会在您尝试查询是否可以和实际可以之间发生变化。在多线程环境中,这是竞争条件的主要原因之一,并且可能是某些程序的真正漏洞。

基本上你只需要尝试创建它,看看它是否有效。这是正确的方法。这就是为什么像ConcurrentHashMap 这样的东西有一个putIfAbsent() 所以检查和插入是一个原子操作并且不会受到竞争条件的影响。这里的原理完全相同。

如果这只是某些诊断或安装过程的一部分,请执行此操作并查看它是否有效。但是,不能保证它以后会起作用。

基本上,您的程序必须足够健壮,才能在无法写入相关文件时优雅地死掉。

【讨论】:

DON"T DIE(在某些情况下),允许用户选择另一个卷或媒体。我有一个 IDE,它在无法写入其项目文件时死机。卷已脱机 -请允许我选择另一个地点并继续。

以上是关于Java中有没有办法在不尝试创建文件的情况下确定路径是不是有效?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在不读取整个文件的情况下推断文件是啥图像格式?

有没有办法在不读取整个文件的情况下推断文件是啥图像格式?

有没有办法在不读取整个文件的情况下推断文件是啥图像格式?

有没有办法确定跨域图像是不是会在不绘制画布的情况下污染画布?

有没有办法在不创建新套接字的情况下处理 CRL 更新

有没有办法在不抛出异常的情况下转储堆栈跟踪?