发布于2026-07-11 阅读(0)
扫一扫,手机访问
你知道吗?Windows 系统虽然已经进化到了 Windows 11,但骨子里还保留着 DOS 时代的一些“老规矩”——CON、NUL、COM1 这些名字,至今仍是文件系统的“禁区”。更让人意外的是,Ja va 的 `mkdir` 和 `Files.createDirectories` 居然能绕过部分校验,成功创建出打着这些“禁区”名字的目录。唯独 `NUL` 是个硬茬子,死活创建不了。这篇文章就来拆解一下:这背后的机制是什么,实际中会带来哪些风险,以及如何做好防护。
在当下的 Windows 中,CON、PRN、AUX、NUL、COM1–COM9、LPT1–LPT9 这些名字,被系统标记为保留设备名。它们的源头可以追溯到 MS-DOS 1.0 时代,那时候这些名字直接对应着硬件端口,比如 PRN 代表打印机,NUL 则是空设备。微软官方的态度很明确:这些名字,不管带不带扩展名,都不能用作文件或文件夹名。但实际情况是,你用 Ja va 的 File.mkdir() 或 Files.createDirectories() 去创建 CON、COM3 或 LPT8 文件夹,它居然返回 true,而且资源管理器里还能看到这些文件夹。唯独 NUL 这个家伙,始终创建失败(exists() 返回 false)。这种看似矛盾的现象,根源其实在于 Windows NT 内核的“双重标准”。
核心在于 Windows 处理路径的层级逻辑。正常情况下,如果你通过常规的 Win32 API(比如 CreateDirectoryW)去创建 C:\Test\CON,系统在解析路径时会立刻识别出 CON 是保留名,然后果断拒绝操作。但 Ja va 的 File.mkdir() 在底层,有些时候会绕过这个检查逻辑,尤其是在启用了长路径支持或使用了 `\\?\` 命名空间的情况下。它可能允许你创建出这个目录,但这个目录会处于一种“半隐式”的怪异状态:能被列出、重命名、删除,但绝大多数 Windows 工具都没法正常访问它。这就好比系统里偷偷藏了一个伪装成管理员账号的家伙,看着像,但权限和功能完全不同。
举个例子,你在命令行里试试这个操作:
cd C:\Testing\CON dir > C:\Testing\CON\test.txt // 你以为在写文件,实际上数据被写到了控制台
瞧,CON 目录虽然存在,但所有指向它的路径操作都会被系统重定向到控制台设备。类似的,PRN 可能触发打印任务,AUX 可能和串口通信扯上关系。这些副作用在自动化脚本、备份工具、IDE 项目扫描或 CI 构建中,会引发一堆隐蔽的故障:构建缓存莫名其妙失效、文件同步静默跳过、JVM 类路径解析报错,严重的时候,IDE(比如 IntelliJ)甚至可能因为无法遍历 COM1 目录而直接卡死,这可不是闹着玩的。
更让人头疼的是跨平台问题。在 Unix/Linux 和 macOS 上,根本不存在“保留关键字”这一说,/tmp/CON 或 /opt/LPT9 都是合法路径。如果你的 Ja va 应用依赖用户输入来动态创建目录(比如 new File(userInput).mkdir()),却没有做任何校验,那么 Windows 上生成的这些“合法但危险”的目录,一旦放到 CI/CD 的 Linux runner 上,或者部署到 macOS 服务器,立马就会因为路径语义的不同而导致功能错乱或安全边界失效。这才是问题的核心所在。
那么,正确的防护方案是什么?这块儿我们可以直接用代码来实现(推荐 Ja va 11+ 版本):
import ja va.nio.file.Path;
import ja va.util.Set;
import ja va.util.stream.Collectors;
public class WindowsReservedNameValidator {
private static final Set RESERVED_NAMES = Set.of(
"CON", "PRN", "AUX", "NUL",
"COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
);
public static boolean isWindowsReserved(Path path) {
String name = path.getFileName().toString().toUpperCase();
// 忽略大小写,同时处理带扩展名的情况(例如 CON.txt 也要拦截)
int dotIndex = name.indexOf('.');
if (dotIndex > 0) name = name.substring(0, dotIndex);
return RESERVED_NAMES.contains(name);
}
// 使用示例
public static void safeMkdir(Path dir) throws IOException {
if (isWindowsReserved(dir)) {
throw new IllegalArgumentException(
"Cannot create directory with Windows reserved name: " + dir);
}
Files.createDirectories(dir);
}
}
这里有几个要点需要特别注意:
总而言之,Windows 的保留名不是什么历史遗迹,而是潜伏在路径操作中的“定时冲击波”。与其等它爆炸了再来排查,不如在编码阶段就把它防住——将其纳入路径合法性校验的基线,这是构建跨平台健壮 Ja va 应用必不可少的一道防线。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8