PAYATHON 2026

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 类系统上通常表示元数据状态变更时间,因此不能一概当作创建时间使用。

备注:内容仅供参考。