Files.copy默认不覆盖目标文件,必须显式传入StandardCopyOption.REPLACE_EXISTING才能覆盖;若目标已存在且未指定该选项,则抛FileAlreadyExistsException。

Files.copy 默认不覆盖目标文件,必须显式指定选项
调用 Files.copy(src, dst) 时,若 dst 已存在,Java 会直接抛出 FileAlreadyExistsException,不会静默跳过或覆盖。这是安全默认行为,但常被误认为“复制失败”。
- 想覆盖:必须传入
StandardCopyOption.REPLACE_EXISTING - 想保留源文件时间戳(如最后修改时间):额外加
StandardCopyOption.COPY_ATTRIBUTES - 这两个选项可同时使用,顺序无关:
Files.copy(src, dst, REPLACE_EXISTING, COPY_ATTRIBUTES) - 注意:
REPLACE_EXISTING对目录无效——目标路径不能是已存在的目录,否则仍抛异常
目标父目录不存在时会报错,得提前创建
Files.copy() 只管文件,不管路径结构。如果 dst 的父目录(比如 "backup/logs/app.log" 中的 "backup/logs")不存在,会立即抛 NoSuchFileException。
- 修复方式:复制前调用
Files.createDirectories(Paths.get(dst).getParent()) -
createDirectories()是幂等的,目录已存在也不报错,比createDirectory()更安全 - 别依赖
mkdirs()(来自旧版File类),它在某些 NFS 或权限受限路径下可能静默失败
覆盖策略不是非黑即白,得结合业务逻辑判断
直接无条件 REPLACE_EXISTING 容易引发数据丢失,尤其在配置文件、用户文档等场景。更稳妥的做法是先检查再决策。
- 跳过已有文件:
if (!Files.exists(dst)) Files.copy(src, dst) - 重命名备份旧文件:
if (Files.exists(dst)) Files.move(dst, dst.resolveSibling(dst.getFileName() + ".bak"), REPLACE_EXISTING) - 校验内容再覆盖:用
Files.mismatch()比较源和目标内容是否一致,避免无意义覆盖 - 所有判断都应包裹在
try-catch中,至少捕获IOException,因为exists()和copy()都可能因权限、磁盘满等问题失败
大文件复制卡主线程?它本身不阻塞线程,但仍是同步操作
Files.copy() 底层走的是操作系统级的高效拷贝(Linux 上可能触发 sendfile,Windows 上用 CopyFileEx),不加载全量数据进堆内存,所以不会 OOM。但它仍是同步调用,执行期间当前线程会挂起等待 I/O 完成。
- UI 应用(如 JavaFX/Swing)务必丢到后台线程:
CompletableFuture.runAsync(() -> Files.copy(...)) - 需要进度反馈?
Files.copy()不提供回调,得退回到FileChannel+transferTo()手动分段读写 - 跨文件系统(如从 SSD 拷到 USB 盘)时,
transferTo()可能回退为普通读写,性能不如预期,此时不如直接用Files.copy()省心
Files.copy() 看似简洁,但生产环境里,getParent() 和 try-catch 这两处几乎必加。

















