files.copy不支持跨卷移动,需手动实现“复制+删除”;须先校验同文件系统,复制时按策略处理覆盖并校验完整性,删除前确保成功且可回滚,路径需预处理非法字符。

Files.copy 本身不支持“移动”,只负责复制。所谓跨卷移动,本质是“先复制、再删除源文件”的两步操作,必须手动实现,并重点防控重名覆盖和中途失败导致数据丢失的风险。
明确跨卷前提:先判断是否同设备
不能假设 Paths.get(src) 和 Paths.get(dst) 在同一磁盘——Windows 的 C: 和 D:、Linux 的 /home 和 /mnt/usb 都属于不同文件系统。直接 Files.move() 会抛 FileSystemException: Invalid cross-device link。
安全做法是提前校验:
- 用 Files.getFileStore(source).equals(Files.getFileStore(target)) 判断底层文件系统是否一致(最准确)
- 或轻量级判断:source.toRealPath().getRoot().equals(target.toRealPath().getRoot())(注意 toRealPath() 会检查路径存在性,若源不存在需先兜底)
复制阶段:覆盖策略与异常防护
目标文件已存在时,是否覆盖由业务决定。常见策略有三种:
- 强制覆盖:传 StandardCopyOption.REPLACE_EXISTING,但要注意——它不会跳过只读文件,Windows 下可能仍失败
- 跳过已有:复制前调用 Files.exists(target),为 true 就直接 continue
- 自动重命名:如目标为 file.txt,检测到存在后生成 file_1.txt → file_2.txt,直到不冲突;推荐用 LinkedHashMap 统一预生成映射表,避免边遍历边改路径引发竞态
无论选哪种,都应在 copy 后立即校验:Files.size(source) == Files.size(target) 且 Files.getLastModifiedTime(source).equals(Files.getLastModifiedTime(target)),确保内容完整、时间戳未被意外篡改。
删除阶段:仅在复制成功后执行,且必须可逆
删除源文件是高危动作,绝不能放在 try 块末尾“无条件执行”:
- 必须等 copy 完成 + 校验通过后,才调用 Files.delete(source)
- 删除失败(如源文件被其他进程锁定)时,不能静默吞掉异常,应记录错误并保留源文件,避免“副本没写全、源又没了”的双失局面
- 如需回滚能力,可先将源文件 renameTo(source + ".backup"),待新文件验证无误后再删 backup —— 这比直接 delete 更稳妥
中文路径与非法字符:运行前主动过滤
Files.copy 对路径合法性极其敏感,遇到 Windows 禁止字符(: * ? " |)、控制字符或编码乱码,会直接抛 InvalidPathException 或 IOException。
建议在构造 target 路径前做预处理:
- 用 Path.getFileName() 提取原始文件名,再用正则替换非法字符(如替换成下划线)
- 避免拼接字符串生成路径,始终用 Paths.get(parent, safeFileName)
- 对用户输入的目录名,也需检查是否为空、是否含 .. 跳转、是否超长(超过 260 字符易在 Windows 触发异常)










