在不将其解压到磁盘的情况下读取压缩文件中的RDS文件。

6

有没有什么原因,让我无法直接读取存储在zip文件中的RDS文件,而不必先将其解压到临时文件上?

假设这是zip文件:

saveRDS(cars, "cars.rds")
saveRDS(iris, "iris.rds")
write.csv(iris, "iris.csv")
zip("datasets.zip", c("cars.rds", "iris.rds", "iris.csv"))
file.remove("cars.rds", "iris.rds", "iris.csv")

对于 csv 文件,我可以像这样直接读取它:

iris2 <- read.csv(unz("datasets.zip", "iris.csv"))

然而,我不明白为什么我不能直接在readRDS()中使用unz()

iris3 <- readRDS(unz("datasets.zip", "iris.rds"))

这给我带来了错误:
Error: unknown input format

我也想了解为什么会出现这种情况。我知道我可以像这个问题中所述的那样做:

path <- unzip("datasets.zip", "iris.rds")
iris4 <- readRDS(path)
file.remove(path)

然而,这种方法似乎不太高效,并且我需要频繁地处理大量文件,因此I/O效率很重要。有没有任何方法可以在不将rds文件提取到磁盘的情况下读取它?

2个回答

14
这有点棘手,直到我阅读了readRDS()的正文之后才找到了解决方法。它似乎需要你执行以下操作:
  1. 使用unz()打开.zip存档和其中的文件。
  2. 使用gzcon()对此连接应用GZIP解压缩。
  3. 最后将此已解压缩的连接传递给readRDS()
这里有一个示例,演示如何在matrix.zip压缩档案中使用以下序列化矩阵mat
mat <- matrix(1:9, ncol = 3)
saveRDS(mat, "matrix.rds")
zip("matrix.zip", "matrix.rds")

打开与 matrix.zip 的连接

con <- unz("matrix.zip", filename = "matrix.rds")

现在,使用gzcon()函数对此连接进行GZIP解压缩。

con2 <- gzcon(con)

最后,从连接中读取数据

mat2 <- readRDS(con2)

完整的我们有

con <- unz("matrix.zip", filename = "matrix.rds")
con2 <- gzcon(con)
mat2 <- readRDS(con2)
close(con2)

这提供了

> con <- unz("matrix.zip", filename = "matrix.rds")
> con2 <- gzcon(con)
> mat2 <- readRDS(con2)
> close(con2)
> mat2
     [,1] [,2] [,3]
[1,]    1    4    7
[2,]    2    5    8
[3,]    3    6    9
> all.equal(mat, mat2)
[1] TRUE

为什么?

为什么你要经过这个复杂的额外步骤,这在 ?readRDS 中(我认为)有描述:

file是一个文件名时,压缩是由打开的连接处理的,因此仅当file是一个连接时才可能由连接处理。 因此,例如url连接将需要在调用gzcon中包装。

如果您查看readRDS()的内部结构,我们可以看到:

> readRDS
function (file, refhook = NULL) 
{
    if (is.character(file)) {
        con <- gzfile(file, "rb")
        on.exit(close(con))
    }
    else if (inherits(file, "connection")) 
        con <- file
    else stop("bad 'file' argument")
    .Internal(unserializeFromConn(con, refhook))
}
<bytecode: 0x2841998>
<environment: namespace:base>
如果file是文件名的字符串,那么使用gzile()解压对象,以创建到我们要读取的.rds的连接。请注意,如果将连接作为file传递(就像你想做的那样),那么R在任何时候都没有解压连接。 file只是分配给con,然后传递给内部函数unserializeFromConn。因此,在由unz创建的连接周围包装gzcon()有效。
基本上,当unserializeFromConn从连接中读取数据时,它期望该连接已被解压缩,但仅当您传递一个文件名而不是连接时,才会自动进行解压缩。

太好了,很有道理!而且它适用于我的原始示例:iris3 <- readRDS(gzcon(unz("datasets.zip", "iris.rds"))) - cocquemas

1

readRDS()的签名是

saveRDS(object, file = "", ascii = FALSE, version = NULL,
    compress = TRUE, refhook = NULL)

但令人沮丧的是,readRDS 的签名中没有任何内容。然而,当你阅读 readRDS 的文档时,你会发现这个小宝石。
## or examine the object via a connection, which will be opened as needed.
con <- gzfile("women.rds")
readRDS(con)
close(con)

"and also"(也)
## Less convenient ways to restore the object
## which demonstrate compatibility with unserialize()
con <- gzfile("women.rds", "rb")
identical(unserialize(con), women)
close(con)
con <- gzfile("women.rds", "rb")
wm <- readBin(con, "raw", n = 1e4) # size is a guess
close(con)
identical(unserialize(wm), women)

考虑使用压缩对RDS对象进行优化的收益是什么。
X <- matrix(rnorm(1e7), ncol=10)
saveRDS(X, file = "X.rds")
system("cp X.rds XZ.rds")
system("gzip XZ.rds")
uncomp <- file.info("X.rds")$size
comp <- file.info("XZ.rds.gz")$size
savings <- (1 - comp/uncomp)
# [1] -0.00030541

因此,举个典型的例子,压缩RDS对象会占用我们的空间。


是的,您可以直接通过gzfile访问rds文件,但如果rds文件在zip文件中,则无法访问,这是我的问题。我也知道将xz压缩的rds文件一起压缩成zip文件可能会有一些磁盘空间的损失,不幸的是这是我接收数据的方式。 - cocquemas
啊,那真不幸。只是想要帮忙而已。我也曾经处理过不太理想的数据格式。祝你好运! - Shawn Mehan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接