Java调用FileUtils.copyDirectory复制SMB网络文件速度远低于系统文件管理器
解决Java FileUtils复制SMB共享NAS文件速度慢的问题
我之前处理过类似的SMB共享文件复制性能问题,用Apache Commons IO的FileUtils.copyDirectory确实会比Windows资源管理器或Nautilus慢不少,最高只有8MB/s的速度,主要根源在这几点:
FileUtils默认使用较小的缓冲区(默认是4KB),而且是单线程逐文件复制,完全没利用到SMB协议的批量传输、多线程优化特性- 系统自带的文件管理器底层调用的是系统级SMB客户端,做了大量协议层面的优化——比如调整SMB窗口大小、多线程并发传输、缓存策略优化这些,天生就比通用Java IO实现高效
下面给你几个实用的解决办法,按推荐程度排序:
1. 直接调用系统级复制命令(最推荐,速度和资源管理器一致)
如果追求和系统工具一样的传输速度,直接调用系统自带的复制工具是最优解,这些工具专门针对SMB做了深度优化:
Windows系统:用Robocopy
Robocopy是Windows原生的高性能文件复制工具,对SMB共享的优化非常到位,还支持断点续传:
import java.io.IOException; public class SmbCopyUtil { public static void copyWithRobocopy(String nasDir, String localDir) throws IOException { // /E 复制所有子目录(包括空目录);/J 用无缓冲I/O复制大文件,适合SMB String command = String.format("robocopy \"%s\" \"%s\" /E /J", nasDir, localDir); Process process = Runtime.getRuntime().exec(command); try { int exitCode = process.waitFor(); // Robocopy的退出码0-7都属于正常完成,大于8才是真正的错误 if (exitCode > 8) { throw new IOException("Robocopy复制失败,退出码:" + exitCode); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("复制操作被中断", e); } } }
Linux/macOS系统:用Rsync
Rsync是类Unix系统下的经典工具,对网络文件传输的优化极强,速度基本和Nautilus持平:
import java.io.IOException; public class SmbCopyUtil { public static void copyWithRsync(String nasDir, String localDir) throws IOException { // -a 归档模式(保留权限、时间戳等元数据);-v 显示进度;-h 人类可读格式 String command = String.format("rsync -avh \"%s/\" \"%s\"", nasDir, localDir); Process process = Runtime.getRuntime().exec(command); try { int exitCode = process.waitFor(); if (exitCode != 0) { throw new IOException("Rsync复制失败,退出码:" + exitCode); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("复制操作被中断", e); } } }
2. 优化Java IO的复制逻辑,增大缓冲区
如果不想依赖系统命令,可以自定义复制方法,把缓冲区调大(比如256KB或512KB),减少SMB的IO交互次数:
import java.io.*; public class FastSmbCopy { // 调整为256KB缓冲区,比FileUtils默认的4KB大很多 private static final int BUFFER_SIZE = 256 * 1024; public static void copyDirectory(File srcNasDir, File destLocalDir) throws IOException { if (!destLocalDir.exists()) { destLocalDir.mkdirs(); } File[] files = srcNasDir.listFiles(); if (files == null) return; for (File file : files) { File destFile = new File(destLocalDir, file.getName()); if (file.isDirectory()) { copyDirectory(file, destFile); } else { copyLargeFile(file, destFile); } } } private static void copyLargeFile(File src, File dest) throws IOException { try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(src), BUFFER_SIZE); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dest), BUFFER_SIZE)) { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } out.flush(); } } }
这个方法能把速度提升到接近系统工具的60%-70%,适合必须用纯Java实现的场景。
3. 使用Java NIO的Files类
Java NIO的Files.copy底层调用系统级IO接口,比传统IO更高效,尤其是对网络文件系统:
import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.io.IOException; public class NioSmbCopy { public static void copyDirectory(String nasPath, String localPath) throws IOException { Files.walk(Paths.get(nasPath)) .forEach(srcPath -> { // 构建目标路径,保持目录结构 Path destPath = Paths.get(localPath, srcPath.subpath(Paths.get(nasPath).getNameCount(), srcPath.getNameCount()).toString()); try { Files.copy(srcPath, destPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { e.printStackTrace(); } }); } }
额外的SMB客户端优化
除了代码调整,还可以修改系统的SMB客户端设置进一步提速:
- Windows:打开网卡属性→Internet协议版本4→高级→WINS,启用「TCP/IP上的NetBIOS」;也可以通过注册表调整SMB的接收缓冲区大小(搜“Windows SMB 性能优化”能找到具体步骤)
- Linux:修改
/etc/samba/smb.conf,添加socket options = TCP_NODELAY IPTOS_LOWDELAY SO_RCVBUF=131072 SO_SNDBUF=131072,增大缓冲区
内容的提问来源于stack exchange,提问作者Jon_C
相关产品推荐
相关产品推荐

