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 上找到带有附加测试的所有示例。