1. 概述
在本教程中,我们将讨论 使用 Java 验证给定 字符串 是否具有操作系统有效文件名的 不同方法。我们想要根据受限字符或长度限制检查该值。
通过示例,我们将只关注核心解决方案,而不使用任何外部依赖项。我们将检查SDK的 java.io 和NIO2包,最终实现我们自己的解决方案。
2.使用 java.io.File
让我们从第一个示例开始,使用 java.io.File 类。在这个解决方案中,我们需要使用给定的字符串创建一个 File 实例,然后在本地磁盘上创建一个文件:
public static boolean validateStringFilenameUsingIO(String filename) throws IOException {
File file = new File(filename);
boolean created = false;
try {
created = file.createNewFile();
return created;
} finally {
if (created) {
file.delete();
}
}
}
当给定的文件名不正确时,它 会抛出 IOException 。 请注意,由于内部创建文件,此方法要求给定的 文件名 字符串 与已存在的文件不对应。
我们知道不同的文件系统有自己的 文件名限制 。因此,通过使用 java.io.File 方法, 我们不需要为每个操作系统指定规则 ,因为 Java 会自动为我们处理它。
但是,我们需要创建一个虚拟文件。当我们成功的时候,一定要 记得最后删除它 。此外,我们必须确保我们拥有执行这些操作的适当权限。任何失败也可能导致 IOException ,因此最好检查错误消息:
assertThatThrownBy(() -> validateStringFilenameUsingIO("baeldung?.txt"))
.isInstanceOf(IOException.class)
.hasMessageContaining("Invalid file path");
3. 使用NIO2 API
众所周知, java.io 包有很多缺点,因为它是在 Java 的第一个版本中创建的。 NIO2 API, java.io 包的继承者,带来了很多改进,这也大大简化了我们之前的解决方案:
public static boolean validateStringFilenameUsingNIO2(String filename) {
Paths.get(filename);
return true;
}
我们的函数现在已经简化,因此这是执行此类测试的最快方法。我们不创建任何文件,因此 不需要任何磁盘权限并在测试后执行清理 。
无效的文件名 抛出 InvalidPathException , 它扩展了 RuntimeException 。该 错误消息还包含比前一条更多的详细信息 :
assertThatThrownBy(() -> validateStringFilenameUsingNIO2(filename))
.isInstanceOf(InvalidPathException.class)
.hasMessageContaining("character not allowed");
该解决方案有一个 与文件系统限制相关的严重缺点 。 Path 类可能表示带有子目录的文件路径。与第一个示例不同,此方法不检查文件名字符的溢出限制。让我们对照使用 Apache Commons 的 randomAlphabetic() 方法生成的 500 个字符的随机 字符串 来检查它:
String filename = RandomStringUtils.randomAlphabetic(500);
assertThatThrownBy(() -> validateStringFilenameUsingIO(filename))
.isInstanceOf(IOException.class)
.hasMessageContaining("File name too long");
assertThat(validateStringFilenameUsingNIO2(filename)).isTrue();
为了解决这个问题,我们应该像以前一样创建一个文件并检查结果。
4. 自定义 实现
最后,让我们尝试实现我们自己的自定义函数来测试文件名。我们还将尝试避免任何 I/O 功能并仅使用核心 Java 方法。
这些类型的解决方案提供了更多的控制权,并允许我们 实施我们自己的规则 。然而,我们 必须考虑不同系统的许多额外限制 。
4.1.使用 String.contains
我们可以 使用 String.contains() 方法 来检查给定的 字符串 是否包含任何禁止的字符。首先,我们需要手动指定一些示例值:
public static final Character[] INVALID_WINDOWS_SPECIFIC_CHARS = {'"', '*', '<', '>', '?', '|'};
public static final Character[] INVALID_UNIX_SPECIFIC_CHARS = {'\000'};
在我们的示例中,我们只关注这两个操作系统。正如我们所知, Windows 文件名比 UNIX 受到更多限制 。此外,某些 空白字符可能会出现问题 。
定义受限制的字符集后,让我们确定当前的操作系统:
public static Character[] getInvalidCharsByOS() {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
return INVALID_WINDOWS_SPECIFIC_CHARS;
} else if (os.contains("nix") || os.contains("nux") || os.contains("mac")) {
return INVALID_UNIX_SPECIFIC_CHARS;
} else {
return new Character[]{};
}
}
现在我们可以用它来测试给定的值:
public static boolean validateStringFilenameUsingContains(String filename) {
if (filename == null || filename.isEmpty() || filename.length() > 255) {
return false;
}
return Arrays.stream(getInvalidCharsByOS())
.noneMatch(ch -> filename.contains(ch.toString()));
}
如果我们定义的任何字符不在给定文件名中,则此 Stream 谓词返回 true。此外,我们还实现了对 空 值和不正确长度的支持。
4.2.正则表达式模式匹配
我们还可以直接在给定的 String 上 使用正则表达式 。让我们实现一个仅接受字母数字和点字符的模式,长度不大于 255:
public static final String REGEX_PATTERN = "^[A-Za-z0-9.]{1,255}$";
public static boolean validateStringFilenameUsingRegex(String filename) {
if (filename == null) {
return false;
}
return filename.matches(REGEX_PATTERN);
}
现在,我们可以根据之前准备的模式测试给定值。我们还可以轻松修改图案。在此示例中,我们跳过了操作系统检查功能。
5. 结论
在本文中,我们重点讨论文件名及其限制。我们引入了不同的算法来使用 Java 检测无效文件名。
我们从 java.io 包开始,它 为我们解决了大部分系统限制,但执行额外的 I/O 操作 ,并且可能需要一些权限。然后我们检查了NIO2 API,这 是最快的解决方案,有文件名长度检查限制 。
最后,我们实现了自己的方法, 不使用任何 I/O API,但需要文件系统规则的自定义实现 。
您可以在 GitHub 上找到带有附加测试的所有示例。