Rust: Rust 标准库关键类型:时间、环境与进程
最后更新:2026-08-26
Rust 标准库(std)提供了操作系统交互的核心类型——时间测量、环境变量、进程管理、路径操作。掌握这些类型,你的 Rust 程序就能真正与系统对话。
如果 Rust 语言本身是"发动机",那标准库就是"方向盘、仪表盘和导航系统"。没有标准库,你的程序只能做纯计算(加减乘除、字符串拼接)。有了标准库,程序才能:知道现在几点、读取命令行的参数、执行外部命令、操作文件路径——变成一个真正的"应用程序"。
1. 你将学到
std::time::Duration和std::time::Instant——时间段测量与程序计时std::time::SystemTime——获取和操作系统时间std::env::args——读取命令行参数std::env::var——读取和设置环境变量std::process::Command——执行外部命令并获取输出std::path::Path和PathBuf——跨平台路径操作
2. 定时备份脚本的故事
(1) 痛苦:手动备份的痛苦
Tom 是一名运维工程师,每天下班前要手动备份服务器上的重要数据。这个流程让他非常苦恼:
- 他得登录到服务器,手动敲
tar命令 - 备份文件的名字要带上日期——
backup_20260703.tar.gz,他经常敲错 - 备份路径保存在环境变量中,但每次都得
echo $BACKUP_PATH查看 - 他想写一个自动化脚本,但每次都不知道程序跑了多久
- 不同操作系统上路径分隔符不一样——Windows 用
\,Linux 用/
"如果能写一个 Rust 程序:自动获取当前时间、读取环境变量里的备份路径、执行压缩命令、记录处理耗时……一切就完美了。"
(2) Rust 标准库的方案
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use std::env;
use std::process::Command;
fn main() {
let start = Instant::now();
println!("=== 定时备份脚本 ===");
// 1. 获取当前系统时间
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
println!("[时间] 当前 Unix 时间戳: {}s", now);
// 2. 读取环境变量
let backup_dir = env::var("BACKUP_DIR")
.unwrap_or_else(|_| String::from("./backup"));
let db_name = env::var("DB_NAME")
.unwrap_or_else(|_| String::from("my_database"));
println!("[配置] 备份目录: {}", backup_dir);
println!("[配置] 数据库名: {}", db_name);
// 3. 构造备份文件名
let archive_name = format!("{}/backup_{}.tar.gz", backup_dir, now);
// 4. 执行无害的外部命令(模拟备份)
let output = Command::new("echo")
.arg(format!("Simulating backup of {} to {}", db_name, archive_name))
.output()
.expect("命令执行失败");
println!("[执行] 命令输出: {}", String::from_utf8_lossy(&output.stdout));
// 5. 计算耗时
let elapsed = start.elapsed();
println!("[耗时] 操作用时: {:?}", elapsed);
println!("=== 备份完成 ===");
}
这个不到 30 行的脚本展示了 Rust 标准库的核心能力:
Instant测量耗时、SystemTime获取系统时间、env::var读取环境变量、Command执行外部命令。每一个类型都是从操作系统获取信息或控制系统的"窗口"。
3. 核心概念
(1) 标准库关键类型体系
graph TB
A[Rust 标准库关键类型] --> B[时间相关]
A --> C[环境相关]
A --> D[进程相关]
A --> E[路径相关]
B --> B1["Duration: 时间段"]
B --> B2["Instant: 瞬时计时"]
B --> B3["SystemTime: 系统时钟"]
C --> C1["env::args(): 命令行参数"]
C --> C2["env::var(): 环境变量"]
C --> C3["env::set_var(): 设置变量"]
D --> D1["Command::new"]
D --> D2["status() / output()"]
D --> D3["stdin / stdout / stderr"]
E --> E1["Path: 不可变路径切片"]
E --> E2["PathBuf: 可增长的路径"]
E --> E3["join / parent / exists"]
(2) 关键类型对比
| 类型/函数 | 所属模块 | 用途 | 关键方法 |
|---|---|---|---|
Duration |
std::time |
时间段(如 5 秒、100 毫秒) | from_secs, from_millis, as_secs |
Instant |
std::time |
程序启动以来的单调计时 | now, elapsed, duration_since |
SystemTime |
std::time |
系统时钟时间(可被 NTP 调整) | now, duration_since, UNIX_EPOCH |
env::args |
std::env |
读取命令行参数 | 返回 Args 迭代器 |
env::var |
std::env |
读取环境变量 | 返回 Result<String, VarError> |
Command |
std::process |
执行外部命令 | new, arg, output, status |
PathBuf |
std::path |
可修改的文件路径 | push, pop, set_extension |
Path |
std::path |
不可变的路径引用 | exists, is_file, parent, join |
(3) Path 与 PathBuf 的关系
Path |
PathBuf |
|
|---|---|---|
| 类似字符串类型 | &str(不可变引用) |
String(拥有所有权) |
| 可变性 | 不可变(只读操作) | 可变(push、pop 等) |
| 内存位置 | 栈或堆上的引用 | 堆上的缓冲区 |
| 常用操作 | exists, is_dir, parent, join |
push, pop, set_extension |
| 转换 | path.to_path_buf() → PathBuf |
path_buf.as_path() → &Path |
(4) 时间类型对比速查
| 类型 | 用途 | 单调递增 | 精度 | 可被系统调整 | 典型用法 |
|---|---|---|---|---|---|
Instant |
程序内计时 | 是 | 纳秒 | 否 | 测量代码耗时 |
SystemTime |
系统时钟时间 | 否 | 纳秒 | 是(NTP 等) | 获取当前日期时间 |
Duration |
时间段/间隔 | N/A | 纳秒 | N/A | 超时、睡眠、加减运算 |
Path和PathBuf的关系完全类比&str和String:PathBuf是拥有所有权的可变路径,Path是借用的不可变路径切片。所有路径操作(join、parent等)都在Path上定义。
4. 标准库关键类型示例
▶ 示例 1:时间测量——Duration、Instant 与 SystemTime(难度 ⭐)
// ============================================
// 演示:std::time 模块的三种时间类型
// 模拟:程序计时器和系统时钟
// ============================================
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
fn main() {
println!("=== 时间类型演示 ===\n");
// --- 1. Duration:时间段 ---
let five_secs = Duration::from_secs(5);
let hundred_ms = Duration::from_millis(100);
let two_mins = Duration::from_secs(2 * 60);
println!("[Duration] 5秒 = {:?}", five_secs);
println!("[Duration] 100ms = {:?}", hundred_ms);
println!("[Duration] 2分钟 = {:?}", two_mins);
// Duration 运算:加减和比较
let total = five_secs + hundred_ms;
println!("[Duration] 5秒+100ms = {:?}", total);
println!("[Duration] 5秒 > 100ms = {}", five_secs > hundred_ms);
// --- 2. Instant:程序计时 ---
let start = Instant::now();
// 模拟一些耗时操作
let mut sum: u64 = 0;
for i in 0..1_000_000 {
sum = sum.wrapping_add(i);
}
let elapsed = start.elapsed();
println!("\n[Instant] 计算 sum={} 耗时: {:?}", sum, elapsed);
println!("[Instant] 耗时毫秒: {}ms", elapsed.as_millis());
println!("[Instant] 耗时微秒: {}µs", elapsed.as_micros());
// --- 3. SystemTime:系统时间 ---
let now = SystemTime::now();
let since_epoch = now.duration_since(UNIX_EPOCH).unwrap();
println!("\n[SystemTime] 当前时间戳: {} 秒", since_epoch.as_secs());
println!("[SystemTime] 当前时间戳: {} 毫秒", since_epoch.as_millis());
// 计算一天前的时间
let one_day_ago = now - Duration::from_secs(24 * 60 * 60);
let since_epoch_ago = one_day_ago.duration_since(UNIX_EPOCH).unwrap();
println!("[SystemTime] 一天前的时间戳: {} 秒", since_epoch_ago.as_secs());
println!("\n=== 时间演示结束 ===");
}
输出:
=== 时间类型演示 ===
[Duration] 5秒 = 5s
[Duration] 100ms = 100ms
[Duration] 2分钟 = 120s
[Duration] 5秒+100ms = 5.1s
[Duration] 5秒 > 100ms = true
[Instant] 计算 sum=499999500000 耗时: 2.345ms
[Instant] 耗时毫秒: 2ms
[Instant] 耗时微秒: 2345µs
[SystemTime] 当前时间戳: 1783012250 秒
[SystemTime] 当前时间戳: 1783012250123 毫秒
[SystemTime] 一天前的时间戳: 1782925850 秒
=== 时间演示结束 ===
Duration表示时间段(如 "5 秒"),支持加减和比较运算。Instant是"程序启动以来的单调计时器"——适合测量代码执行时间,不受系统时间调整影响。SystemTime读取系统时钟(可被 NTP 调整),适合获取当前日期时间。duration_since(UNIX_EPOCH)获取 Unix 时间戳。
▶ 示例 2:环境与命令行参数——env::args 和 env::var(难度 ⭐⭐)
// ============================================
// 演示:std::env 模块读取命令行参数和环境变量
// 模拟:一个可配置的备份工具
// ============================================
use std::env;
fn main() {
println!("=== 环境与参数探查器 ===\n");
// --- 1. 读取命令行参数 ---
let args: Vec<String> = env::args().collect();
println!("[args] 命令行参数数量: {}", args.len());
if args.len() > 1 {
println!("[args] 程序名: {}", args[0]);
println!("[args] 传入参数:");
for (i, arg) in args.iter().enumerate().skip(1) {
println!(" args[{}] = {}", i, arg);
}
} else {
println!("[args] 没有传入额外参数");
println!("[args] 提示: 运行方式如下");
println!(" cargo run -- --mode=backup --target=./data");
}
// --- 2. 读取环境变量 ---
println!("\n--- 环境变量探查 ---");
// 尝试读取几个常见的环境变量
check_env_var("PATH");
check_env_var("HOME");
check_env_var("USER");
check_env_var("BACKUP_DIR"); // 可能不存在
check_env_var("DB_CONNECTION"); // 可能不存在
// --- 3. 读取并解析自定义配置 ---
println!("\n--- 配置解析 ---");
// 设置一个环境变量用于演示(仅在当前进程有效)
// 注意:set_var 在实际项目中谨慎使用,会影响当前进程
env::set_var("MY_APP_MODE", "backup");
env::set_var("MY_APP_VERBOSE", "true");
let mode = env::var("MY_APP_MODE").unwrap_or_else(|_| "default".to_string());
let verbose = env::var("MY_APP_VERBOSE")
.map(|v| v == "true" || v == "1")
.unwrap_or(false);
println!("[配置] 运行模式: {}", mode);
println!("[配置] 详细输出: {}", verbose);
// --- 4. 遍历所有环境变量(仅演示前几个)---
println!("\n--- 环境变量列表(前5个)---");
for (i, (key, value)) in env::vars().enumerate().take(5) {
println!(" {} = {}", key, value);
}
println!("\n=== 探查结束 ===");
}
/// 尝试读取环境变量并打印
fn check_env_var(name: &str) {
match env::var(name) {
Ok(val) => println!("[env] {} = {}", name, val),
Err(env::VarError::NotPresent) => println!("[env] {} = (未设置)", name),
Err(env::VarError::NotUnicode(_)) => println!("[env] {} = (非 UTF-8 值)", name),
}
}
输出:
=== 环境与参数探查器 ===
[args] 命令行参数数量: 1
[args] 没有传入额外参数
[args] 提示: 运行方式如下
cargo run -- --mode=backup --target=./data
--- 环境变量探查 ---
[env] PATH = C:\Windows\system32;C:\Windows;...
[env] HOME = (未设置)
[env] USER = (未设置)
[env] BACKUP_DIR = (未设置)
[env] DB_CONNECTION = (未设置)
--- 配置解析 ---
[配置] 运行模式: backup
[配置] 详细输出: true
--- 环境变量列表(前5个)---
ALLUSERSPROFILE = C:\ProgramData
APPDATA = C:\Users\Administrator\AppData\Roaming
...
=== 探查结束 ===
env::args()返回命令行参数迭代器,第一个元素是程序路径。env::var(name)读取环境变量,返回Result<String, VarError>——可能因为变量不存在(NotPresent)或值不是有效 Unicode(NotUnicode)而失败。env::set_var可以设置环境变量(仅当前进程有效)。env::vars()遍历所有环境变量。
▶ 示例 3:Command 与 Path——执行外部命令和路径操作(难度 ⭐⭐)
// ============================================
// 演示:std::process::Command 与 std::path::Path/PathBuf
// 模拟:备份脚本——构建路径、执行压缩命令
// ============================================
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
println!("=== 路径操作与外部命令演示 ===\n");
// --- 1. Path 和 PathBuf 基本操作 ---
println!("--- 路径操作 ---");
// 创建 PathBuf(拥有所有权的可变路径)
let mut backup_dir = PathBuf::new();
backup_dir.push("data");
backup_dir.push("backups");
println!("[PathBuf] 初始路径: {}", backup_dir.display());
// 设置文件扩展名
let mut archive = backup_dir.clone();
archive.push("database");
archive.set_extension("tar.gz");
println!("[PathBuf] 归档文件: {}", archive.display());
// Path 的只读操作
let path = Path::new("C:/Users/Alice/Documents/report.pdf");
println!("\n[Path] 文件路径: {}", path.display());
println!("[Path] 文件名: {:?}", path.file_name());
println!("[Path] 扩展名: {:?}", path.extension());
println!("[Path] 父目录: {:?}", path.parent());
println!("[Path] 是否存在: {}", path.exists());
// 路径拼接
let docs = Path::new("./docs");
let full_path = docs.join("rust").join("guide.md");
println!("[Path] 拼接路径: {}", full_path.display());
// --- 2. 使用 Command 执行外部命令 ---
println!("\n--- 外部命令执行 ---");
// 示例 A:执行 "echo" 命令(无害命令)
let echo_result = Command::new("echo")
.arg("Hello from Rust! Backup process started.")
.output()
.expect("echo 命令执行失败");
println!("[Command] echo 输出: {}",
String::from_utf8_lossy(&echo_result.stdout));
// 示例 B:执行 "whoami" 命令(显示当前用户)
let whoami_output = Command::new("whoami")
.output()
.expect("whoami 命令执行失败");
if whoami_output.status.success() {
let username = String::from_utf8_lossy(&whoami_output.stdout);
println!("[Command] 当前用户: {}", username.trim());
} else {
let err = String::from_utf8_lossy(&whoami_output.stderr);
println!("[Command] whoami 失败: {}", err);
}
// 示例 C:检查命令是否成功(status 模式)
let status = Command::new("cmd")
.args(["/C", "dir", "./"])
.status()
.expect("dir 命令执行失败");
if status.success() {
println!("[Command] dir 命令执行成功 (exit code: {})", status.code().unwrap_or(-1));
} else {
println!("[Command] dir 命令执行失败 (exit code: {:?})", status.code());
}
// --- 3. 组合使用:根据路径构建命令 ---
println!("\n--- 模拟备份流程 ---");
let source = Path::new("./data/documents");
let dest = Path::new("./backup/documents_backup.tar.gz");
println!("备份源: {}", source.display());
println!("备份目标: {}", dest.display());
// 模拟 tar 命令(实际不会执行,只是展示命令构造方式)
let _mock_cmd = Command::new("tar")
.arg("-czf")
.arg(dest)
.arg(source)
.status();
// 实际执行一个无害的展示命令
let show_cmd = Command::new("echo")
.arg(format!(
"Would execute: tar -czf {} {}",
dest.display(),
source.display()
))
.output()
.unwrap();
println!("模拟命令: {}",
String::from_utf8_lossy(&show_cmd.stdout).trim());
println!("\n=== 演示结束 ===");
}
输出:
=== 路径操作与外部命令演示 ===
--- 路径操作 ---
[PathBuf] 初始路径: data\backups
[PathBuf] 归档文件: data\backups\database.tar.gz
[Path] 文件路径: C:/Users/Alice/Documents/report.pdf
[Path] 文件名: Some("report.pdf")
[Path] 扩展名: Some("pdf")
[Path] 父目录: Some("C:/Users/Alice/Documents")
[Path] 是否存在: false
[Path] 拼接路径: docs\rust\guide.md
--- 外部命令执行 ---
[Command] echo 输出: Hello from Rust! Backup process started.
[Command] 当前用户: DESKTOP-ABC123\Administrator
[Command] dir 命令执行成功 (exit code: 0)
--- 模拟备份流程 ---
备份源: ./data/documents
备份目标: ./backup/documents_backup.tar.gz
模拟命令: Would execute: tar -czf ./backup/documents_backup.tar.gz ./data/documents
=== 演示结束 ===
Command::new("程序名")创建一个新命令,通过arg和args添加参数。output()捕获 stdout 和 stderr(适合需要处理输出的场景),status()只检查退出码(适合不需要输出的场景)。Path::display()以跨平台方式显示路径。PathBuf是Path的可变所有权版本,支持push、pop、set_extension等修改操作。path.join(...)返回一个新的PathBuf。
▶ 示例 4:综合练习——CLI 工具:文件信息查看器(难度 ⭐⭐⭐)
// ============================================
// 综合示例:std::fs + std::path + std::time
// ============================================
use std::env;
use std::fs;
use std::path::Path;
use std::time::SystemTime;
fn format_size(size: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = 1024 * KB;
const GB: u64 = 1024 * MB;
if size >= GB { format!("{:.2} GB", size as f64 / GB as f64) }
else if size >= MB { format!("{:.2} MB", size as f64 / MB as f64) }
else if size >= KB { format!("{:.2} KB", size as f64 / KB as f64) }
else { format!("{} B", size) }
}
fn format_time(systime: SystemTime) -> String {
let duration = systime.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
let secs = duration.as_secs();
let days = secs / 86400;
let hours = (secs % 86400) / 3600;
let minutes = (secs % 3600) / 60;
format!("{}天 {:02}:{:02} UTC", days, hours, minutes)
}
fn inspect_file(path_str: &str) -> Result<(), Box<dyn std::error::Error>> {
let path = Path::new(path_str);
if !path.exists() {
println!("路径不存在: {}", path.display());
return Ok(());
}
println!("=== 文件信息 ===");
println!("路径: {}", path.display());
println!("文件名: {:?}", path.file_name());
println!("扩展名: {:?}", path.extension());
println!("父目录: {:?}", path.parent());
if path.is_file() {
let metadata = fs::metadata(path)?;
println!("大小: {}", format_size(metadata.len()));
println!("只读: {}", metadata.permissions().readonly());
println!("修改时间: {}", format_time(metadata.modified()?));
} else if path.is_dir() {
println!("类型: 目录");
let entries: Vec<_> = fs::read_dir(path)?
.filter_map(|e| e.ok())
.collect();
println!("条目数: {}", entries.len());
for entry in entries.iter().take(10) {
let name = entry.file_name().to_string_lossy().to_string();
let tag = if entry.path().is_dir() { "DIR" } else { "FILE" };
println!(" [{}] {}", tag, name);
}
if entries.len() > 10 {
println!(" ... 还有 {} 个条目", entries.len() - 10);
}
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
let target = if args.len() > 1 { &args[1] } else { "." };
inspect_file(target)
}
输出(运行 cargo run -- src/main.rs):
=== 文件信息 ===
路径: src/main.rs
文件名: Some("main.rs")
扩展名: Some("rs")
父目录: Some("src")
大小: 1.23 KB
只读: false
修改时间: 20504天 08:30 UTC
此示例综合运用了
std::fs(文件元数据)、std::path(路径操作)、std::time(时间格式化)和std::env(命令行参数)。format_size友好显示文件大小,format_time将SystemTime转为可读时间。
▶ 示例 5:综合练习——简单计时器与系统信息(难度 ⭐⭐)
// ============================================
// 综合示例:std::time + std::env + std::process
// ============================================
use std::env;
use std::process::Command;
use std::time::{Duration, Instant};
struct Timer {
start: Instant,
label: String,
}
impl Timer {
fn new(label: &str) -> Self {
println!("[{}] 计时开始", label);
Timer { start: Instant::now(), label: label.to_string() }
}
fn elapsed(&self) -> Duration {
self.start.elapsed()
}
fn stop(self) -> Duration {
let elapsed = self.elapsed();
println!("[{}] 计时结束: {:.3}秒", self.label, elapsed.as_secs_f64());
elapsed
}
}
fn main() {
println!("=== 系统信息 ===");
println!("当前目录: {:?}", env::current_dir().unwrap_or_default());
println!("操作系统: {}", env::consts::OS);
println!("架构: {}", env::consts::ARCH);
println!("\n=== 环境变量 ===");
for key in &["HOME", "PATH", "USER", "LANG"] {
match env::var(key) {
Ok(val) => println!("{}: {} 字符", key, val.len()),
Err(_) => println!("{}: (未设置)", key),
}
}
println!("\n=== 计时演示 ===");
let t1 = Timer::new("计算斐波那契");
let mut fib: Vec<u64> = vec![0, 1];
for _ in 0..45 {
let next = fib[fib.len()-1] + fib[fib.len()-2];
fib.push(next);
}
t1.stop();
println!("fib(45) = {}", fib[45]);
let t2 = Timer::new("字符串操作");
let mut s = String::new();
for i in 0..10_000 {
s.push_str(&format!("item{} ", i));
}
let len = s.len();
t2.stop();
println!("字符串长度: {} 字符", len);
println!("\n=== 进程退出码 ===");
let status = Command::new("cmd")
.args(&["/C", "echo", "hello"])
.status();
match status {
Ok(s) => println!("退出码: {}", s.code().unwrap_or(-1)),
Err(e) => println!("执行失败: {}", e),
}
}
输出:
=== 系统信息 ===
当前目录: "G:\\..."
操作系统: windows
架构: x86_64
=== 环境变量 ===
HOME: 20 字符
PATH: 500 字符
...
=== 计时演示 ===
[计算斐波那契] 计时开始
[计算斐波那契] 计时结束: X.XXX秒
fib(45) = 1134903170
[字符串操作] 计时开始
[字符串操作] 计时结束: X.XXX秒
字符串长度: 88889 字符
=== 进程退出码 ===
退出码: 0
Timer利用Instant的 RAII 模式自动计时;env::consts获取平台信息;env::var读取环境变量;Command执行外部命令并获取退出码。
❓ 常见问题
Instant 和 SystemTime 有什么区别?什么时候用哪个?Instant 是单调递增的计时器,SystemTime 读取系统时钟。env::var 和 env::args 返回的字符串编码是什么?String。Command::output() 和 Command::status() 有什么区别?output() 捕获 stdout 和 stderr,status() 只检查退出码。Path 和 PathBuf 是跨平台的,内部使用平台特定的路径分隔符。Command 执行命令时,PATH 环境变量会被自动搜索吗?Command::new("程序名") 会搜索 PATH 环境变量。📖 小节
Duration表示时间段(秒、毫秒、微秒),支持加减、比较运算;Instant是单调计时器,适合测量耗时;SystemTime读取系统时钟,适合获取时间戳env::args()返回命令行参数迭代器,第一个元素是程序路径;env::var(name)读取环境变量,返回Result<String, VarError>env::set_var设置环境变量(仅当前进程有效),生产中谨慎使用;env::vars()遍历所有环境变量Command::new执行外部命令,arg/args添加参数,output()捕获输出,status()检查退出码PathBuf是拥有所有权的可变路径(类似String),支持push、pop、set_extension等修改Path是借用的不可变路径切片(类似&str),支持exists、parent、join、file_name等只读操作
📝 作业
- 难度 ⭐:写一个程序,使用
Instant测量一个循环(1 到 1,000,000 累加)的执行时间,并分别以毫秒(ms)和微秒(µs)打印耗时。同时使用SystemTime获取程序启动时的 Unix 时间戳并打印。 - 难度 ⭐⭐:实现一个"环境变量查看器"。通过命令行参数传入一个环境变量名称,程序读取并打印其值。如果变量不存在,打印友好提示。额外功能:如果没有传入参数,列出所有以
MY_或RUST_开头的环境变量及其值。 - 难度 ⭐⭐⭐:实现一个"简易备份工具"。使用
PathBuf构建源目录和目标归档路径。通过环境变量BACKUP_SOURCE和BACKUP_DEST读取配置(未设置则使用默认值./data和./backup/archive.tar.gz)。使用Command执行echo命令模拟备份流程——打印"Backing up [source] to [dest]"。在开始和结束时使用Instant记录并打印总耗时。要求使用Path的exists()检查源目录是否存在。