跨分区移动无法保证原子性,因操作系统限制rename仅在同一文件系统内有效;跨分区实际为复制+删除两阶段操作,需手动校验与容错处理。

在 NIO.2 中,Paths.get 本身只是构造 Path 对象,不参与移动逻辑;真正决定是否“原子”移动的是 Files.move() 的实现机制和底层文件系统支持。跨磁盘分区(即不同文件系统挂载点)的移动**无法保证原子性**,这是操作系统层面的限制,NIO.2 无法绕过。
为什么跨分区移动不可能原子
原子移动(如 Linux 的 rename(2) 系统调用)仅在**同一文件系统内有效**。跨分区时,实际执行的是“复制 + 删除”两阶段操作:
- 先将源文件内容完整复制到目标位置(可能耗时、失败、中断)
- 仅当复制成功后,才删除原文件
- 若中途崩溃或断电,可能出现源文件已删、目标文件不完整的情况
StandardCopyOption 的作用与局限
StandardCopyOption.ATOMIC_MOVE 仅在满足以下全部条件时才启用原子重命名:
- 源和目标位于同一文件系统(
statfs或GetVolumeInformation判定) - 目标路径不存在,或目标是空目录(Windows 下有额外限制)
- 底层 OS 支持(Linux/macOS 通常支持;Windows 对跨卷 rename 返回错误)
若不满足,NIO.2 会自动回退为普通复制+删除,并忽略 ATOMIC_MOVE 选项——不会抛异常,也不会警告。
如何安全处理跨分区移动
既然无法原子,就需主动容错。推荐分步控制:
-
预检查分区:用
Files.getFileStore(source)和Files.getFileStore(target.getParent())比较name()或toString(),判断是否同文件系统 -
同分区走 ATOMIC_MOVE:
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE) -
跨分区手动复制+校验+删除:
- 用
Files.copy(source, tempTarget, StandardCopyOption.REPLACE_EXISTING) - 校验大小、哈希(如
Files.size()+MessageDigest) - 校验通过后,再
Files.delete(source) - 任一环节失败,清理临时文件并保留源文件
- 用
代码片段示意
注意:以下不包含完整异常处理与日志,仅展示核心逻辑分支
Path source = Paths.get("/mnt/disk1/data.txt");
Path target = Paths.get("/mnt/disk2/backup.txt");
try {
FileStore srcStore = Files.getFileStore(source);
FileStore tgtStore = Files.getFileStore(target.getParent());
if (srcStore.name().equals(tgtStore.name())) {
// 同分区:尝试原子移动
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
} else {
// 跨分区:安全复制+删除
Path tempTarget = target.resolveSibling(target.getFileName() + ".tmp");
Files.copy(source, tempTarget, StandardCopyOption.REPLACE_EXISTING);
if (Files.size(tempTarget) == Files.size(source)) { // 基础校验
Files.move(tempTarget, target, StandardCopyOption.REPLACE_EXISTING);
Files.delete(source);
} else {
Files.deleteIfExists(tempTarget);
throw new IOException("Copy size mismatch");
}
}
} catch (IOException e) {
// 处理失败(如磁盘满、权限不足等)
}










