为什么某些内存地址报告不变而其他地址发生变化

pic*_*ick 5 memory r memory-address

我一直在尝试使用data.table::address或跟踪内存中的各种对象.Internal(address()),但是注意到有些对象每次返回相同的地址,而其他对象几乎总是不同.这里发生了什么?

我注意到像list(data.tables,data.frames等)这样的对象的地址保持不变(由这些函数报告),而如果我尝试将地址报告[到列表中,即address(lst[1])几乎得到不同的结果每次.另一方面,lst[[1]]返回相同的值,并且常量的地址address(pi)保持不变,address(1)而是volatile.为什么会这样?

## Create some data.tables of different sizes and plot the addresses
library(data.table)
par(mfrow = c(2,2))
for (i in 2:5) {
    dat <- data.table(a=1:10^i)
    ## Constants
    addr1 <- address(dat)
    addr2 <- address(dat[[1]])
    addr3 <- address(dat$a)  # same as addr2
    ## Vary
    addrs <- replicate(5000, address(dat[1]))
    plot(density(as.integer(as.hexmode(addrs))), main=sprintf("N: %g", nrow(dat)))
    abline(v=as.integer(as.hexmode(c(addr1, addr2, addr3))), col=1:3, lwd=2, lty=1:3)
    legend("topleft", c("dat", "dat[[1]]", "dat$a"), col=1:3, lwd=2, lty=1:3)
}
Run Code Online (Sandbox Code Playgroud)

以下是我正在谈论的不同大小的data.tables的一些例子.它们只是结果的密度address(dat[1])(转换为整数),并且这些行对应于data.table的常量地址.

在此输入图像描述

J R*_*ape 3

首先,我可以复制你的结果,所以我做了一些调查并深入研究了一些代码。

dat当您访问using的第一个成员时,您实际上是在创建一个由in或dat[1]组成的切片。要获取切片,R 首先复制列表,然后返回所需的切片。 listdata[[1]]dat$a

所以 - 基本上 - 你会看到你所看到的,因为[]索引语法返回一个包含第一个元素的切片,dat 该切片的第一个元素是 的副本dat$a,它将位于任意内存位置。

[[]]语法返回对实际列表的引用,该列表是您的data.table或中的列data.frame,因此其地址是不变的(或者至少在您更改该列表的成员之前是不变的)。

这可能会令人困惑,因为这样做dat[1] = 6或类似操作当然会改变数据结构中列表的值。但是,如果您查看address(dat[[1]])进行此类更改之前和之后,您会注意到实际上现在引用的是不同的列表(副本),例如

> dat <- data.table(a=1:10000)
> dat
           a
    1:     1
    2:     2
    3:     3
    4:     4
    5:     5
   ---      
 9996:  9996
 9997:  9997
 9998:  9998
 9999:  9999
10000: 10000
> address(dat[[1]])
[1] "000000000CF389D8"
> address(dat[[1]])
[1] "000000000CF389D8"
> dat[1] = 100
> address(dat[[1]])
[1] "000000000D035B38"
> dat
           a
    1:   100
    2:     2
    3:     3
    4:     4
    5:     5
   ---      
 9996:  9996
 9997:  9997
 9998:  9998
 9999:  9999
10000: 10000
> 
Run Code Online (Sandbox Code Playgroud)

查看data.frame(而不是) 的源代码,执行切片索引 ( )data.table的代码在这里,而直接索引 ( )在这里。您可以看到后者更简单,长话短说,前者返回一个副本。如果直接更改切片(例如),这里有一些逻辑可以确保数据框现在引用更新的副本。[][[]]dat[1] = 5