R 递归列出文件并获取元数据太慢,如何优化?
数据阿明
结论
需要获取每个文件的大小和时间信息时,就要读取文件系统元数据。对大量文件调用 file.info(),耗时通常取决于磁盘或网络存储的速度,以及文件数量。可以先减少不必要的元数据读取,再考虑按顶层子目录并行处理。并行不一定能提速:本地磁盘可能因争用而变慢,网络盘或响应延迟较高的存储有时更适合并发访问。
先确认是否需要全部元数据
如果只需要文件名、路径和扩展名,可以跳过 file.info():
files <- list.files(
path = parent_folder,
recursive = TRUE,
full.names = TRUE
)
df_files <- data.frame(
file_name = basename(files),
extension = tools::file_ext(files),
parent_folder = dirname(files),
full_path = files,
stringsAsFactors = FALSE
)
如果需要 size 或 ctime,就要获取相应的文件元数据。当前代码会一次性为所有路径生成完整信息表,适用于文件数量可控且需要这些列的情况。
按顶层子目录并行处理
顶层有多个子目录时,可以把每个子目录分配给一个 worker。下面的示例使用 R 自带的 parallel 包和 PSOCK 集群,也适用于 Windows:
parent_folder <- "C:/Users/Name/Documents/Folder"
collect_info <- function(paths) {
info <- file.info(paths)
data.frame(
file_name = basename(paths),
date_created = info$ctime,
size = info$size,
extension = tools::file_ext(paths),
parent_folder = dirname(paths),
full_path = paths,
stringsAsFactors = FALSE
)
}
collect_dir <- function(dir) {
paths <- list.files(
path = dir,
recursive = TRUE,
full.names = TRUE
)
collect_info(paths)
}
entries <- list.files(parent_folder, full.names = TRUE)
top_dirs <- entries[dir.exists(entries)]
root_files <- entries[!dir.exists(entries)]
# 直接处理根目录中的文件
parts <- list(collect_info(root_files))
if (length(top_dirs) > 0) {
available_cores <- parallel::detectCores(logical = FALSE)
if (is.na(available_cores)) available_cores <- 2L
workers <- min(available_cores, length(top_dirs))
if (workers > 1L) {
cl <- parallel::makeCluster(workers)
parallel::clusterExport(
cl,
c("collect_info", "collect_dir"),
envir = environment()
)
dir_parts <- tryCatch(
parallel::parLapply(cl, top_dirs, collect_dir),
finally = parallel::stopCluster(cl)
)
} else {
dir_parts <- lapply(top_dirs, collect_dir)
}
parts <- c(parts, dir_parts)
}
df_files <- do.call(rbind, parts)
如果目录为空,或根目录下没有文件,合并结果可能没有行。将这段代码封装成通用函数时,可以单独处理零文件的情况。这个方案按顶层子目录分配任务,因此顶层目录较少时,可用的 worker 数量也会受限。
如何判断并行是否值得
可以先选取一小部分目录,对比串行和并行的耗时,再决定是否扩大扫描范围。测试结果不能保证某种方式一定更快,实际耗时取决于文件数量、目录结构和存储设备。worker 数量不要超过任务数;对于本地机械硬盘或繁忙的磁盘,增加并发可能会降低性能。
还要注意,file.info() 返回的 ctime 在不同操作系统上的含义可能不同:在 Windows 上通常对应创建时间,在 Unix 类系统上通常表示元数据状态变更时间,因此不能一概当作创建时间使用。
备注:内容仅供参考。
