从R写入Excel时处理java.lang.OutOfMemoryError


82

xlsx软件包可用于读取和写入R中的Excel电子表格。不幸的是,即使对于中等大小的电子表格,java.lang.OutOfMemoryError也可能会发生。尤其是,

.jcall(“ RJavaTools”,“ Ljava / lang / Object;”,“ invokeMethod”,cl,中的错误:
java.lang.OutOfMemoryError:Java堆空间

.jcall(“ RJavaTools”,“ Ljava / lang / Object;”,“ newInstance”,.jfindClass(class),中的错误:
java.lang.OutOfMemoryError:超出了GC开销限制

(其他相关例外也是可能的,但很少见。)

在读取电子表格时,针对此错误提出了类似的问题。

导入大xlsx文件到R?

与CSV相比,使用Excel电子表格作为数据存储介质的主要优点是可以将多个工作表存储在同一个文件中,因此在这里我们考虑将一个数据帧列表写入每个工作表一个数据帧。此示例数据集包含40个数据帧,每个数据帧包含两列,每行最多200k行。它的大小设计得足以出现问题,但是您可以通过更改n_sheets和来更改大小n_rows

library(xlsx)
set.seed(19790801)
n_sheets <- 40
the_data <- replicate(
  n_sheets,
  {
    n_rows <- sample(2e5, 1)
    data.frame(
      x = runif(n_rows),
      y = sample(letters, n_rows, replace = TRUE)
    )
  },
  simplify = FALSE
)
names(the_data) <- paste("Sheet", seq_len(n_sheets))

将其写入文件的自然方法是使用创建工作簿createWorkbook,然后循环调用createSheet和调用每个数据帧addDataFrame。最后,可以使用将工作簿写入文件saveWorkbook。我已将消息添加到循环中,以使其更容易查看掉落的位置。

wb <- createWorkbook()  
for(i in seq_along(the_data))
{
  message("Creating sheet", i)
  sheet <- createSheet(wb, sheetName = names(the_data)[i])
  message("Adding data frame", i)
  addDataFrame(the_data[[i]], sheet)
}
saveWorkbook(wb, "test.xlsx")  

在具有8GB RAM的计算机上以64位运行此命令,则在首次GC overhead limit exceeded运行时会引发错误addDataFrame

如何使用将大型数据集写入Excel电子表格xlsx

Answers:


78

这是一个已知问题:http : //code.google.com/p/rexcel/issues/detail?id=33

尽管尚未解决,但问题页面链接到Gabor Grothendieck的解决方案,建议通过java.parametersrJava装入程序包之前设置选项来增加堆大小。(rJava是的依赖项xlsx。)

options(java.parameters = "-Xmx1000m")

该值1000是允许Java堆使用的RAM的兆字节数;可以用您喜欢的任何值替换它。我的实验表明,更大的值更好,您可以愉快地使用完整的RAM授权。例如,使用以下方法可获得最佳结果:

options(java.parameters = "-Xmx8000m")

在具有8GB RAM的计算机上。

通过在循环的每次迭代中请求垃圾回收,可以获得进一步的改进。如@gjabel所述,可以使用进行R垃圾回收gc()。我们可以定义一个调用JavaSystem.gc()方法的Java垃圾收集函数:

jgc <- function()
{
  .jcall("java/lang/System", method = "gc")
}    

然后可以将循环更新为:

for(i in seq_along(the_data))
{
  gc()
  jgc()
  message("Creating sheet", i)
  sheet <- createSheet(wb, sheetName = names(the_data)[i])
  message("Adding data frame", i)
  addDataFrame(the_data[[i]], sheet)
}

修复了这两个代码后,代码就运行到i = 29抛出错误为止。

我尝试失败的一种技术是write.xlsx2在每次迭代时都将内容写入文件。这比其他代码慢,并且在第10次迭代时失败了(但至少有部分内容已写入文件)。

for(i in seq_along(the_data))
{
  message("Writing sheet", i)
  write.xlsx2(
    the_data[[i]], 
    "test.xlsx", 
    sheetName = names(the_data)[i], 
    append    = i > 1
  )
}

38
现在,可以通过将xlsx软件包替换openxlsx为依赖于RcppJava而不是Java的软件包来回避整个问题。
Richie Cotton

4
readxl是另一个看起来很有希望的新的C / C ++替代方案。
Richie Cotton

1
不幸的是,我发现这两者对于检测和读取日期都是很垃圾的-两者最终都以Excel日期格式陷入混乱:\
MichaelChirico

2
@RichieCotton,不错的选择。但是,openxlsx无法读取.xls或.xlm文件!(2007 Excel文件格式)。
Espanta

调用options(java.parameters = "-Xmx8000m")加载之前rJavaxlsxjarsxlsx解决了Error in .jcall("RJavaTools", "Ljava/lang/Object;", "invokeMethod", cl, : org.apache.poi.POIXMLException: java.lang.reflect.InvocationTargetException Calls: getNetwork ... <Anonymous> -> .jrcall -> .jcall -> .jcheck -> .Call Execution halted在RHEL 6.3 x86_64的,JAVA 1.7.0_79(甲骨文),rJava_0.9-7,xlsxjars_0.6.0,xlsx_0.5.7
尼克董

7

在@ richie-cotton答案的基础上,我发现gc()对该jgc函数进行添加可保持较低的CPU使用率。

jgc <- function()
{
  gc()
  .jcall("java/lang/System", method = "gc")
}    

我以前的for循环仍在为原始jgc功能苦苦挣扎,但是通过额外的命令,我不再遇到GC overhead limit exceeded错误消息。


-1

如果要逐行编写,也可以在循环内使用gc()。gc()代表垃圾回收。gc()可以在任何内存问题的情况下使用。


-1

解决以上错误的方法:请使用以下提到的r-代码:

detach(package:xlsx)
detach(package:XLConnect)
library(openxlsx)

并且,尝试再次导入文件,您将不会得到任何错误,因为它对我有用。


两条评论:xlConnect有相同的问题。更重要的是,告诉某人使用其他库并不是解决引用该库的问题的解决方案。这里的目标是保留在xlsx软件包中。XLConnect还有其他线程。
Michael Tuchman

-1

我遇到了write.xlsx()问题,而不是阅读问题....但后来意识到我不小心正在运行32位R。将其交换为64位可以解决此问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.