Scala中如何等待下载、解压、删除的Process链执行完成,替代Thread.sleep硬等待
现有实现的核心问题
- 硬编码睡眠机制可靠性为0:网速波动、磁盘IO性能差异下,要么等待时间过长浪费线程资源,要么前序步骤未完成就触发后续操作,导致文件损坏、删除源文件报错
- 完全缺失流程校验:没有判断每一步执行的退出状态,就算下载断连、zip包损坏、解压权限不足导致步骤失败,程序还是会按固定时间等待后继续执行后续逻辑,极易产生脏数据
- 管道符串接写法失效的原因:Scala
sys.process的###操作符是将前一个进程的标准输出作为后一个进程的标准输入,而下载、解压、删除三个步骤根本不存在输入输出流的依赖关系,强行串接会导致流提前关闭、执行顺序完全不符合预期
优化实现思路
移除不必要的阻塞等待
Scala sys.process提供的!方法本身就是阻塞等待对应进程完全执行结束、返回进程退出码的,方法返回的时刻,对应进程已经释放完所有资源、操作完成,完全不需要额外加Thread.sleep做硬等待。之前需要加睡眠本质是对API行为的误解。
每一步执行完成后必须校验返回的退出码,非0值时直接抛出异常终止整个流程,不继续执行后续操作。
减少外部进程依赖
尽量把下载、解压、删除逻辑放到JVM进程内完成,减少跨进程调用的额外开销,同时避免不同操作系统缺少对应命令、参数不兼容的问题:
- 下载步骤直接用JDK自带的IO流将远程文件拷贝到本地,不需要靠
URL #> file的进程重定向实现 - 解压步骤如果要兼容所有环境,可以直接用JDK内置的
ZipFile类读取zip包内容,筛选出需要的json文件写入磁盘,不需要依赖系统自带的unzip命令 - 删除操作直接用Java NIO的
Files.deleteIfExists实现,不需要调用外部rm命令
修正后的示例代码
import java.io.File import java.net.URL import java.nio.file.{Files, Paths, StandardCopyOption} import scala.concurrent.{ExecutionContext, Future} import scala.sys.process._ def downloadZipThenExtract(link: String, filePath: String)(implicit ec: ExecutionContext): Future[Int] = Future { val zipFile = Paths.get(filePath).toFile val targetDir = zipFile.getParentFile // 步骤1:JVM内直接完成文件下载 val remoteStream = new URL(link).openStream() try { Files.copy(remoteStream, zipFile.toPath, StandardCopyOption.REPLACE_EXISTING) } finally { remoteStream.close() } // 步骤2:执行解压,校验进程退出状态 val unzipExitCode = s"unzip -o ${zipFile.getPath} -d ${targetDir.getPath}".! if (unzipExitCode != 0) { throw new RuntimeException(s"解压流程异常终止,退出码: $unzipExitCode") } // 步骤3:删除本地zip源文件 Files.deleteIfExists(zipFile.toPath) 0 }
额外注意事项
- 可以将每一步逻辑拆分为独立的
Future,通过flatMap实现顺序串接,不需要在单个阻塞线程内跑完所有流程,也能更精准地捕获每一步的执行异常 - 大文件下载时需要设置合理的读写缓冲区,避免全量加载文件到内存,500MB级别的文件用8KB~16KB的缓冲区逐块读写即可控制内存占用
内容的提问来源于stack exchange,提问作者AminMal
相关产品推荐
相关产品推荐

