不使用递归实现FTP服务器目录文件遍历列出的方法
FTP目录遍历递归改非递归实现方案及优化建议
核心实现原理
递归遍历目录的本质是JVM自动维护方法调用栈,每个栈帧存储当前待处理的目录路径。非递归实现只需要手动维护一个存储待处理目录的容器,模拟这个调用栈即可,完全可以实现和递归一致的遍历效果,还能避免目录层级过深时JVM栈溢出的问题。
常用的两种遍历模式:
- 深度优先遍历:和原递归逻辑顺序完全一致,用栈结构存储待处理目录,后进先出,优先处理最新发现的子目录
- 广度优先(层序)遍历:用队列存储待处理目录,先进先出,先处理完当前层级所有目录和文件,再往下处理子目录
非递归实现代码(深度优先,匹配原递归遍历顺序)
以下代码直接修复了原递归代码里的显性bug:
import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayDeque; import java.util.Deque; private static void listFolderNonRecursive(FTPClient ftpClient, String remoteRootPath, String localSaveRoot) throws IOException { // 用ArrayDeque模拟栈,比传统Stack类性能更高,存储所有待遍历的远端目录 Deque<String> pendingDirs = new ArrayDeque<>(); // 初始化时把根目录压入栈 pendingDirs.push(remoteRootPath); while (!pendingDirs.isEmpty()) { String currentDir = pendingDirs.pop(); System.out.println("Listing folder " + currentDir); FTPFile[] remoteFiles = ftpClient.listFiles(currentDir); // 空值防御:权限不足、目录不存在、网络波动时listFiles可能返回null if (remoteFiles == null || remoteFiles.length == 0) { continue; } for (FTPFile remoteFile : remoteFiles) { String fileName = remoteFile.getName(); // 跳过.和..特殊目录 if (".".equals(fileName) || "..".equals(fileName)) { continue; } String remoteFullPath = currentDir + "/" + fileName; if (remoteFile.isDirectory()) { // 子目录压入栈等待后续处理 pendingDirs.push(remoteFullPath); // 提前创建本地对应的目录,避免下载文件时父目录不存在报错 String relativePath = remoteFullPath.substring(remoteRootPath.length()); File localDir = new File(localSaveRoot, relativePath); if (!localDir.exists()) { localDir.mkdirs(); } } else { System.out.println("Found file " + remoteFullPath); String relativePath = remoteFullPath.substring(remoteRootPath.length()); File localFile = new File(localSaveRoot, relativePath); // try-with-resources自动关闭流,避免资源泄漏 try (OutputStream os = new BufferedOutputStream(new FileOutputStream(localFile))) { ftpClient.retrieveFile(remoteFullPath, os); } } } } }
代码优化建议
- 修复原代码的显性bug
- 原代码大括号不匹配,特殊目录的判断块没有正确闭合,导致子目录也会走到下载逻辑,直接调用
retrieveFile下载目录会抛出IO异常 - 原代码中本地存储根路径
directory是未定义的魔法变量,应该作为方法入参显式传入,提升代码可读性 - 原代码的文件输出流没有关闭,会导致文件句柄泄漏,长时间运行会耗尽系统资源
- 原代码大括号不匹配,特殊目录的判断块没有正确闭合,导致子目录也会走到下载逻辑,直接调用
- 性能优化
- 不要使用继承自Vector的
Stack类做栈结构,它的所有方法都加了synchronized锁,单线程场景下性能差,推荐用ArrayDeque替代 - 如果遍历的目录文件量极大,可以把目录遍历和文件下载拆成两个逻辑,用生产者-消费者模式分线程处理,避免遍历耗时太长导致FTP控制连接超时断开
- 不要使用继承自Vector的
- 鲁棒性优化
- 增加FTP返回码校验,调用
listFiles、retrieveFile后检查返回状态,遇到权限不足、连接断开等错误时做重试或明确抛出异常,不要静默失败 - 路径拼接可以增加冗余斜杠处理,避免手动拼接时出现
//这类无效路径;大文件下载可以调整缓冲区大小、增加断点续传逻辑,降低网络波动的影响 - 如果需要调整为层序遍历,只需要把栈的
push/pop操作替换为队列的offer/poll即可,核心遍历逻辑不需要改动
- 增加FTP返回码校验,调用
内容的提问来源于stack exchange,提问作者Ajm Kir
相关产品推荐
相关产品推荐

