You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

read_csv读取大量CSV文件时因“打开文件过多”崩溃的解决方法

问题:批量读取大量小型CSV文件时read_csv崩溃的解决方法?

问题背景

我有多个文件夹,每个文件夹下有约10000个小型CSV文件,希望快速将它们读入内存并合并为每个文件夹对应一个数据框。readr包的read_csv可直接接受文件路径向量并自动合并,非常便捷,但读取大量文件时会崩溃。

可复现示例

continents <- c("africa", "americas", "asia", "europe", "oceania")
filepaths <- vapply(
  paste0("mini-gapminder-", continents, ".csv"),
  FUN = readr_example,
  FUN.VALUE = character(1)
)
filepaths_10k <- rep(filepaths, 2000)
# 可正常运行
read_csv(filepaths, id = "file")
# 运行失败
read_csv(filepaths_10k, id = "file")

报错信息

Error in file(con, "r") : cannot open the connection                                                                                    
In addition: Warning message:
In file(con, "r") :
  cannot open file '/usr/lib/rstudio/resources/CITATION': Too many open files
Error in file(con, "rb") : cannot open the connection                                                                                   
In addition: Warning message:
In file(con, "rb") :
  cannot open file '/home/simon/R/x86_64-pc-linux-gnu-library/3.6/readr/extdata/mini-gapminder-asia.csv': Too many open files

补充说明

  • 曾尝试用lapply+read_csv+rbindlist的组合代码,但运行一整晚都未完成;基准测试显示直接用read_csv的方法速度快得多。
  • 后续补充:显式设置read_csv禁用惰性求值(lazy = FALSE)对速度无影响,也无法解决报错,推测是Ubuntu 20.04的系统特定问题。readr已默认切回立即求值,且后续需要做数据清理,原本也不需要惰性求值。

基准测试结果

microbenchmark(l_apply_rbindlist = lapply(filepaths, read_csv) %>% rbindlist(),
               l_apply_bindrows = lapply(filepaths, read_csv) %>% bind_rows(),
               read_csv_map = map_df(filepaths, ~read_csv(.)),
               readr_default = read_csv(filepaths),
               readr_eager_expl = read_csv(filepaths, lazy = FALSE),
               times = 10,
               check = "equivalent")

输出:

Unit: milliseconds
              expr       min        lq      mean    median        uq       max neval cld
 l_apply_rbindlist 214.08594 219.90338 223.36077 222.36070 227.47078 232.48656    10   b
  l_apply_bindrows 225.47465 232.00539 235.62815 234.78071 239.32159 249.53793    10   b
      read_csv_map 215.86775 225.37601 229.41726 231.70719 232.17263 240.49416    10   b
     readr_default  57.66125  59.77418  77.79516  60.41160  69.10050 214.88023    10  a 
  readr_eager_expl  56.21319  57.05472  61.06905  62.67377  63.66434  64.61471    10  a 

最佳解决方法

方法1:分批次读取+合并

针对系统文件句柄限制,最稳妥的方式是分批次读取,既保留read_csv批量读取的高效性,又避免打开过多文件。

示例代码:

library(readr)
library(data.table)

# 定义批次大小,比如每次处理1000个文件
batch_size <- 1000
file_list <- split(filepaths_10k, ceiling(seq_along(filepaths_10k)/batch_size))

# 分批次读取并合并
combined_df <- rbindlist(lapply(file_list, function(batch) {
  read_csv(batch, id = "file")
}))

原理:将10k文件路径拆分成多个小批次,每个批次调用read_csv读取,最后用rbindlist合并所有批次结果。这样每个批次打开的文件数远低于系统限制,同时每个批次内依然利用read_csv的底层高效合并逻辑,速度比单文件循环快很多。

方法2:临时调整系统文件句柄限制(不推荐长期使用)

如果系统允许,可临时提升用户的文件句柄上限:

  • 在终端执行:ulimit -n 20000(临时生效,重启后恢复)
  • 若要永久修改,需编辑/etc/security/limits.conf,添加:
    soft nofile 20000
    hard nofile 20000
    
    然后重启系统或重新登录。

但这种方法只是绕过系统限制,并非从代码层面解决问题,且可能因系统环境限制无法修改,因此优先推荐分批次读取。

方法3:使用vroom包替代(推荐)

vroom是readr团队开发的新一代高效文件读取包,处理大量小文件时的资源管理更优,且速度与readr相当甚至更快,默认不会一次性打开所有文件:

library(vroom)
combined_df <- vroom(filepaths_10k, id = "file")

vroom采用流式读取机制,不会一次性打开所有文件句柄,能避免"Too many open files"错误,同时保持高效的合并性能,是当前场景下的最优选择之一。


内容的提问来源于stack exchange,提问作者Simon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 08:40:25