【问题标题】:Java - How to find out whether a File name is valid? [duplicate]Java - 如何确定文件名是否有效? [复制]
【发布时间】:2010-10-27 23:54:51
【问题描述】:

在我的 Java 应用程序中,我将文件重命名为字符串参数中提供的文件名。有一种方法

boolean OKtoRename(String oldName, String newName)

它基本上检查 newName 是否还没有被其他文件占用,因为我不想掩埋现有的。

我现在想到,也许 newName 字符串不会表示有效的文件名。所以我想把这个检查添加到方法中:

if (new File(newName).isFile()) { 
    return false; 
}

这显然不是正确的做法,因为在大多数情况下 newFile 还不存在,因此虽然它 OKtoRename,但该函数返回 false。

我想知道,有没有像 canExist() 这样的方法(我知道没有 java.io.File 对象)?还是我必须求助于正则表达式来确保 newFile 字符串不包含无效字符(例如?、*、“、:)?我想知道 JDK 中是否隐藏了一个函数可以告诉我字符串可能表示一个有效的文件名。

【问题讨论】:

  • 拜托,没有更多 exists() 答案,这没有帮助。我正在尝试检查文件是否可能存在。
  • @MasterPeter:试着改写你的问题。最初看起来您想知道文件是否存在,而您真正需要的是知道文件 NAME 是否有效。我更改了标题,但没有触及问题的措辞。
  • 为什么这被标记为重复?指向副本的链接指示检查目录。这里的问题是关于检查文件。

标签: java file


【解决方案1】:

使用createNewFile(),仅当文件尚不存在时才会自动创建文件。

如果文件已创建,则名称有效且不会破坏现有文件。然后,您可以使用FileChannel.transferXXX 操作打开文件并有效地将数据从一个复制到另一个。

需要记住的重要一点是,一般来说,检查和创建应该是原子的。如果先检查一个操作是否安全,然后将操作作为一个单独的步骤执行,此时条件可能发生了变化,导致操作不安全。

在此相关帖子中提供了其他值得思考的内容:"Move/Copy operations in Java."


更新:

从这个答案开始,引入了 NIO.2 API,增加了与文件系统的交互。

假设您有一个交互式程序,并希望在每次击键后验证文件是否可能有效。例如,您可能希望仅在条目有效时启用“保存”按钮,而不是在按下“保存”后弹出错误对话框。创建并确保删除我上面的​​建议所需要的大量不必要的文件似乎是一团糟。

使用 NIO.2,您无法创建包含对文件系统非法的字符的 Path 实例。只要您尝试创建Path,就会引发InvalidPathException

但是,没有用于验证由有效字符组成的非法名称的 API,例如 Windows 上的“PRN”。作为一种解决方法,实验表明在尝试访问属性时使用非法文件名会引发明显的异常(例如,使用Files.getLastModifiedTime())。

如果您为确实存在的文件指定合法名称,则不会出现异常。

如果您为不存在的文件指定合法名称,则会引发NoSuchFileException

如果您指定了非法名称,则会引发 FileSystemException

但是,这看起来很笨拙,在其他操作系统上可能不可靠。

【讨论】:

  • 不过,您需要删除刚刚创建的文件,因为 OKtoRename 函数不应该实际更改文件系统,只需回答文件名是否有效。
  • @Matt: 噢,你刚刚创建了文件,如果 createNewFile 返回 false,则表示文件名无效(...或无法创建:-S)¬¬
  • 抱歉关注并发;我最初将其解释为主要问题。但是,实际创建文件是查看名称是否有效的唯一方法。如果您有兴趣尝试这条路线,可以查看我关于不同平台上保留名称的问题stackoverflow.com/questions/122400/…
  • @Matt Poush, @MasterPeter:我不认为埃里克森建议创建一个文件然后删除它。似乎正确的方法是尝试一步重命名文件,而不是先测试“ok”。如果该步骤失败,那就不行了。如果成功就OK了。
  • @erickson 感谢您对此进行调查。您使用路径的建议似乎类似于在另一个答案中提出的 f.getCanonicalPath() 解决方案,从那时起我就采用了该解决方案并按照我的意愿行事 - 但对该答案的评论说它不适用于 Android。我将尝试使用 nio 功能,这可能是未来的方式。
【解决方案2】:

几个月前,我根据一些在线研究收集了一份非法文件名字符列表(考虑 UNIX、Mac OS X 和 Windows 系统)。如果新文件名包含其中任何一个,则可能存在并非在所有平台上都有效的风险。

private static final char[] ILLEGAL_CHARACTERS = { '/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':' };

编辑: 我想强调,这不是一个完整的解决方案:正如评论者指出的那样,即使它通过了此测试,您的文件名仍然可能是 Windows 特定的关键字,如 COM、PRN 等。但是,如果您的文件名中包含这些字符中的任何一个,那么在跨平台环境中肯定会造成麻烦。

【讨论】:

  • 但不要忘记关键字,比如尝试创建一个名为 COM、COM1、COM2、PRN 等的文件(至少在 Windows 平台上)
  • 点了,还没想过……
  • 虽然我意识到这不是一个完整的解决方案,但这对我来说非常有用(其中只有一部分文件名是动态的,因此关键字不是风险)。感谢您编制这份清单。
  • 添加了警告,表明这只是部分解决方案。
  • @ZsoltTörök 感谢您的列表。本文可能对寻求 Windows 官方文档的人有所帮助:support.microsoft.com/kb/177506
【解决方案3】:

对我来说,这似乎是一个依赖于操作系统的问题。您可能只是想检查文件名中的某些无效字符。当您尝试重命名文件时,Windows 会执行此操作,它会弹出一条消息,指出文件不能包含以下任何字符: \ / : * ? | 我不确定您的问题是否是“有图书馆为我做这项工作吗?”在那种情况下,我什么都不知道。

【讨论】:

  • 还应该检查文件名的长度。
【解决方案4】:

使用

String validName = URLEncoder.encode( fileName , "UTF-8");

File newFile = new File( validName );

做的工作。

我今天才发现。我不确定它是否 100% 有效,但到目前为止,我已经能够创建有效的文件名。

【讨论】:

  • 抱歉,如果文件名中有星号,您的代码将无济于事。 System.out.println(URLEncoder.encode("hello world", "UTF-8"));打印出 hello+world 这不是一个有效的文件名。
  • 太棒了!..你是对的,其实我正要问这个。 :)
【解决方案5】:

我是这样实现的:

public boolean isValidFileName(final String aFileName) {
    final File aFile = new File(aFileName);
    boolean isValid = true;
    try {
        if (aFile.createNewFile()) {
            aFile.delete();
        }
    } catch (IOException e) {
        isValid = false;
    }
    return isValid;
}

【讨论】:

  • 这会导致目录遍历安全漏洞。如果文件名来自第三方,这不是一种安全的编码方式。
  • @VishnuPrasadKallummel 目录遍历包括利用用户提供的输入文件名的安全验证/清理不足。您应该在应用程序中有一个不同的层来执行清理。你能解释一下为什么你认为用户输入验证应该混合在一个验证给定路径是否有效的方法中吗?
  • 这种方法本身将是一个目录遍历问题。但是,如果您在调用此方法之前有一个额外的层进行检查,那么它看起来没问题。这是一个自发的评论,因为我没有实际创建文件然后删除它。
【解决方案6】:

建议Here系统专用方式。

public static boolean isFilenameValid(String file) {
  File f = new File(file);
  try {
    f.getCanonicalPath();
    return true;
  } catch (IOException e) {
    return false;
  }
}

【讨论】:

  • 哦,我的错,我没有看到你的答案...
  • 不错的解决方案,但不幸的是它不适用于 Android :-( 即 f.getCanonicalPath() 不会为不正确的文件名引发异常
  • 依赖 not made for the job 的函数不是有风险吗?
  • 不错;但请注意(即使在 Windows 上)这并没有捕捉到file 是(有效)目录名称的情况,例如,以斜杠结尾。如果您也想排除这种情况,请将第 4+5 行替换为 return f.getCanonicalFile().getName().equals(file)
  • @Arend 建议首先检查 f.isFile(),否则可能会通过有效目录。
【解决方案7】:

如果为 Eclipse 开发,请查看org.eclipse.core.internal.resources.OS

public abstract class OS {
   private static final String INSTALLED_PLATFORM;

   public static final char[] INVALID_RESOURCE_CHARACTERS;
   private static final String[] INVALID_RESOURCE_BASENAMES;
   private static final String[] INVALID_RESOURCE_FULLNAMES;

   static {
      //find out the OS being used
      //setup the invalid names
      INSTALLED_PLATFORM = Platform.getOS();
      if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) {
         //valid names and characters taken from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp
         INVALID_RESOURCE_CHARACTERS = new char[] {'\\', '/', ':', '*', '?', '"', '<', '>', '|'};
         INVALID_RESOURCE_BASENAMES = new String[] {"aux", "com1", "com2", "com3", "com4", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ 
               "com5", "com6", "com7", "com8", "com9", "con", "lpt1", "lpt2", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
               "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "nul", "prn"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$
         Arrays.sort(INVALID_RESOURCE_BASENAMES);
         //CLOCK$ may be used if an extension is provided
         INVALID_RESOURCE_FULLNAMES = new String[] {"clock$"}; //$NON-NLS-1$
      } else {
         //only front slash and null char are invalid on UNIXes
         //taken from http://www.faqs.org/faqs/unix-faq/faq/part2/section-2.html
         INVALID_RESOURCE_CHARACTERS = new char[] {'/', '\0',};
         INVALID_RESOURCE_BASENAMES = null;
         INVALID_RESOURCE_FULLNAMES = null;
      }
   }

   /**
    * Returns true if the given name is a valid resource name on this operating system,
    * and false otherwise.
    */
   public static boolean isNameValid(String name) {
      //. and .. have special meaning on all platforms
      if (name.equals(".") || name.equals("..")) //$NON-NLS-1$ //$NON-NLS-2$
         return false;
      if (INSTALLED_PLATFORM.equals(Platform.OS_WIN32)) {
         //empty names are not valid
         final int length = name.length();
         if (length == 0)
            return false;
         final char lastChar = name.charAt(length-1);
         // filenames ending in dot are not valid
         if (lastChar == '.')
            return false;
         // file names ending with whitespace are truncated (bug 118997)
         if (Character.isWhitespace(lastChar))
            return false;
         int dot = name.indexOf('.');
         //on windows, filename suffixes are not relevant to name validity
         String basename = dot == -1 ? name : name.substring(0, dot);
         if (Arrays.binarySearch(INVALID_RESOURCE_BASENAMES, basename.toLowerCase()) >= 0)
            return false;
         return Arrays.binarySearch(INVALID_RESOURCE_FULLNAMES, name.toLowerCase()) < 0;
      }
      return true;
   }
}

【讨论】:

  • 这可以通过org.eclipse.core.resources.IWorkspace.validateName(String, int)访问
【解决方案8】:

我发现,在 java 7 及更高版本中,有一个名为 Paths 的类,它有一个名为 get 的方法,它接受一个或多个 Strings 并抛出

InvalidPathException - 如果路径字符串无法转换为路径

【讨论】:

  • 这个值得一试
  • 需要注意的一点:在基于 unix 的系统上,这些是有效的文件名:“hi!”、“hello 文件”、“真的吗?”在工作中,我们发现这种方法可以让这些通过。
猜你喜欢
  • 1970-01-01
  • 2010-12-19
  • 1970-01-01
  • 2014-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-08
相关资源
最近更新 更多