文章阅读目录大纲
https://github.com/rsharp-lang/R-sharp/tree/master/studio/RData
在最近的工作中,需要将Docker容器内的R环境之中的数据集无缝的串流到下游的.NET Core数据分析环境之中,基于.NET Core代码库进行数据可视化之类的操作。目前在R环境与.NET Core环境之间进行交互仅存在有一个比较出名的R.NET项目。但是对于使用R.NET项目而言,我们只能够在.NET Core环境之中调用R环境做数据分析,并不能够实现R环境调用.NET Core数据分析环境。并且R.NET项目必须要依赖于R环境对应的库文件,所以使用R.NET并不能够满足我们在Docker容器间进行R数据分析环境与.Net Core数据分析环境之间的无缝衔接。
随着自己开发的R#数据分析环境的日益成熟,最近又实现了在.NET Core环境中对R语言的RData数据集的读取代码模块。所以目前完成的开发工作已经足以支撑构建出一个比较成熟的平滑的混合型的数据分析流程框架。在这里主要是想为大家详细的说明一下RData文件的数据格式,以及对应的VisualBasic.NET代码对RData读取的实现。R#/R
对于RData数据集格式的讲解,我将会分开为独立的几篇文章进行讲解:
RData文件格式
RDataRData文件(扩展名通常为 .RData 或 .rda)是R语言用于保存和加载工作空间数据的二进制文件格式。它可以将R中的各种对象(如向量、列表、数据框、函数等)以二进制形式序列化存储,从而方便地保存和恢复R会话中的数据。RData文件采用R的序列化格式,默认使用XDR(External Data Representation,外部数据表示)编码来存储数据,以确保在不同平台和架构间的可移植性。XDR是一种标准的二进制数据编码方式,它使用大端字节序(Big-Endian)来表示整数和浮点数,从而保证数据在不同字节序的计算机上能够正确解读。RData文件自R 1.4.0版本起采用“版本2”的序列化格式,并一直沿用至R 3.5.3。自R 3.5.0开始,R引入了新的“版本3”序列化格式,但为了向后兼容,R仍然支持读取和写入旧版本的文件。
rdata怎么读取?
很明显的,我们只知道rdata文件的读取在R语言里面会非常的容易:
save(xxx, file = "xxx.rda");
load("xxx.rda");
那,现在假设我们需要在其他的编程语言环境中加载Rdata文件呢?
在开始讲解RData文件格式之前,我们需要记住一个非常重要的文件格式要点:RData数据文件是以的形式进行数据存储,并且每一个在链表之中的数据节点对象都有自己的属性信息,类型信息等元数据。明确了RData的链表存储形式的概念之后,对于下文的数据读取的代码理解将会稍微要容易一些了。二叉树+链表
RData文件本质上是一个序列化的字节流,其中包含一个或多个R对象的二进制表示。为了解析RData文件,需要了解其文件头、元数据以及对象存储格式等关键部分的二进制结构。首先来一张图用于尽可能简单的为大家展示一个标准的RData数据文件的文件内容:

文件头(Header)
RData文件的开头是一个简短的文本行,称为文件头。文件头用于标识文件的序列化格式和版本,以便R在加载时能够选择正确的解析器。文件头通常由以下几个部分组成:
- 格式标识:文件头的前几个字符用于标识序列化格式。对于现代的RData文件,最常见的格式标识是 "RDX2\n"(其中 \n 表示换行符)。这个字符串表示文件采用“版本2”的序列化格式,并且使用XDR二进制编码。其他可能的格式标识包括 "RDA2\n"(版本2的ASCII格式)或 "RDX3\n"(版本3的XDR二进制格式)等,具体取决于保存时使用的参数和R版本。文件头的格式标识允许加载程序快速判断文件的类型和版本,从而采用相应的解码策略。
- 版本信息:在格式标识之后,文件头通常还包含有关序列化版本和R版本的信息。例如,在版本2的格式中,文件头可能包含一个整数表示序列化格式的版本号,以及一个表示R的最小兼容版本的整数。这些信息确保加载的R能够识别文件格式,并在必要时进行兼容性检查。
- 其他元数据:文件头有时还包含一些额外的元数据,例如文件中保存的对象数量、是否使用压缩等信息。不过,这些元数据通常以特定格式编码,需要根据版本进行解析。
文件头以换行符结束,其后紧跟着实际的二进制数据内容。在解析RData文件时,首先需要读取并解析文件头,以确定后续数据的格式和版本。例如,如果文件头以 "RDX2\n" 开头,则表示文件使用版本2的XDR二进制格式,后续的数据将以大端字节序存储,需要按照XDR规范进行解析。
元数据(Metadata)
在文件头之后,RData文件通常包含一些元数据,用于描述文件内容或提供解析所需的上下文信息。元数据的具体形式取决于文件的序列化版本和保存时使用的选项。以下是一些常见的元数据元素:
- 对象列表:对于包含多个对象的RData文件,文件头之后可能是一个对象列表或索引。这个列表列出了文件中保存的每个对象的名称和类型,以及它们在文件中的偏移量或长度。这样,加载程序可以按需读取特定对象,而无需线性扫描整个文件。不过,在标准的RData文件中,通常并不显式存储对象列表,而是顺序存储对象,并在加载时依次读取。
- 环境信息:RData文件保存的是R环境中的对象。因此,元数据可能包含有关环境的信息,例如全局环境或特定命名空间。这有助于在加载时将对象恢复到正确的环境中。
- 压缩信息:如果文件在保存时使用了压缩(例如通过 save() 的 compress 参数),元数据会包含压缩算法和压缩后的数据块信息。RData文件支持gzip、bzip2、xz等压缩格式。在这种情况下,文件的实际内容会被压缩存储,元数据则用于指导解压缩过程。
- 字符编码:在较新的R版本中,元数据可能包含字符编码信息(如UTF-8、Latin1等),以确保字符串在加载时能正确解码。这是为了解决不同平台默认编码不同可能带来的问题。
元数据通常以二进制形式存储,并可能采用XDR编码。解析元数据需要根据其结构逐字段读取。例如,一个整数可能占用4字节(XDR int),一个字符串可能先存储长度再存储字符内容等。在版本3的序列化格式中,元数据的结构有所改进,提供了更清晰的字段划分和扩展性。因此,解析元数据时需要参考对应版本的R内部文档或源码,以确保正确解读每个字段的含义和长度。
对象存储格式
RData文件的核心部分是其包含的R对象的二进制表示。每个R对象在文件中都按照一定的格式序列化存储。R的序列化格式基于R的内部数据结构——SEXP(S表达式)。SEXP是R中所有对象的基础类型,包括原子向量、列表、函数、环境等。在序列化时,每个对象被拆解为其类型信息、属性(如果有的话)以及数据本身,然后按照XDR规范编码为字节流。
下面详细分析R对象在RData文件中的存储格式:
对象类型标识
每个序列化的R对象都以一个类型标识开头,用于指示该对象的SEXP类型。R内部使用一个整数来标识不同的SEXP类型,例如 0 表示 NULL(NILSXP),1 表示符号(SYMSXP),2 表示列表(LISTSXP),10 表示逻辑向量(LGLSXP),13 表示整数向量(INTSXP),14 表示实数向量(REALSXP),16 表示字符向量(STRSXP),19 表示通用列表/向量(VECSXP)等等。这些类型标识与R内部定义的 SEXPTYPE 枚举相对应。在解析RData文件时,首先读取这个类型标识,以确定后续数据的结构应如何解释。
对象属性
在R中,许多对象可以附带属性(attributes),例如维度(dim)、名称(names)、类别(class)等。这些属性在序列化时会被保存,并在对象数据之前或之后存储。通常,如果对象有属性,会先序列化一个属性列表(attrlist),再序列化对象本身的数据部分。属性列表本身也是一个R对象(通常是一个列表或符号表),其中包含若干键值对,每个键是一个符号(字符串),值是一个R对象。在解析时,需要识别对象是否有属性,如果有,则先解析属性列表,再解析对象数据。属性的存在会影响对象的解释,例如一个带有 class 属性的向量在R中会被视为特定类的对象(如数据框、因子等)。
对象数据
对象数据部分存储了对象的实际内容。不同类型的对象有不同的数据存储方式:
- 原子向量:对于数值向量(REALSXP)、整数向量(INTSXP)、逻辑向量(LGLSXP)、复数向量(CPLXSXP)等原子向量,数据部分通常是一个连续的数组。首先会存储向量的长度(元素个数),然后依次存储每个元素的值。例如,一个实数向量会先存储一个整数表示长度,接着存储相应数量的双精度浮点数。这些数值采用XDR格式的大端字节序表示。字符向量(STRSXP)稍有不同,它实际上是一个字符指针(CHARSXP)的向量,因此每个元素是一个字符串的引用。在序列化时,通常先存储向量的长度,然后为每个字符串元素存储其长度和字符内容(或者引用一个全局字符串表,具体取决于版本)。
- 列表(VECSXP):列表在R中是一种通用容器,可以包含不同类型的元素。在序列化时,列表会先存储其长度(元素个数),然后依次序列化每个元素。每个元素本身是一个完整的R对象,因此会递归地按照上述格式进行存储。这意味着解析列表时,需要递归地解析每个子对象。列表可以嵌套,形成复杂的数据结构。
- 数据框:数据框在R中本质上是一个列表,其中每个元素是一个等长的向量,并且具有相同的行数。数据框通常还附带 names 属性(列名)和 row.names 属性(行名)。在序列化时,数据框会被当作列表存储,但附加的属性信息确保了加载时能正确重建数据框的结构。解析数据框时,需要先读取其属性(获取列名和行名等信息),然后读取其作为列表的各列数据,最后在VB.NET中可以将这些数据组合成一个数据框对象(例如自定义的 DataFrame 类)。
- 函数(CLOSXP):函数对象在R中是一个闭包(closure),包含形式参数(formals)、函数体(body)和环境(environment)三部分。序列化函数时,这三部分都会被保存。形式参数是一个符号列表,函数体是一个语言对象(LANGSXP),环境是一个环境对象(ENVSXP)。解析函数需要递归地解析这些组成部分,并在VB.NET中构建一个表示函数的对象。需要注意,函数的环境可能引用全局环境或其他已定义对象,这在解析时需要特殊处理(例如通过引用ID来避免循环)。
- 环境(ENVSXP):环境对象在R中是一个符号表,用于存储变量绑定。序列化环境时,会保存其父环境(enclosing environment)和绑定的符号-值对。解析环境需要递归地构建环境链。在VB.NET中,可以将环境表示为一个字典或自定义类,其中键是变量名,值是对应的对象。
- 其他类型:R还有许多其他对象类型,如符号(SYMSXP)、语言对象(LANGSXP)、特殊对象(S4对象)等。这些类型在序列化时都有相应的表示。例如,符号对象会存储其名称(字符串);语言对象(通常是表达式或调用)会存储其操作符和参数列表;S4对象会存储其类信息和槽位(slots)数据。解析这些类型需要根据R内部文档或源码了解其结构,并在VB.NET中实现相应的类来表示它们。
总的来说,R对象的存储格式是一个递归的、类型驱动的结构。每个对象都以类型标识开始,接着是属性(如果有),然后是数据本身。数据部分根据类型可能是简单的数组,也可能是复杂的嵌套结构。在解析时,需要根据类型标识选择相应的解析逻辑,并递归地处理嵌套的对象。
引用与共享对象
在序列化过程中,R会处理对象之间的共享和循环引用。例如,如果两个列表引用了同一个子对象,R在序列化时会确保该子对象只存储一次,并在第二次出现时使用引用标记。这通常通过为每个对象分配一个唯一ID,并在序列化流中遇到已序列化对象时写入引用ID而非完整数据来实现。解析时,需要维护一个已解析对象的字典,当遇到引用标记时,直接返回已解析的对象实例,而不是重复解析。这种机制对于正确重建复杂对象(如包含循环引用的图结构)至关重要。
RData数据编码基础:XDR编码
在RData数据集之中,仅支持Integer和Double这两种基础数值类型。而这两种数值类型都是以XDR编码方案存储的。
XDR编码方案是 1987 年 6 月由 Sun Microsystems,Inc. 编写的 RFC 1014中描述的外部数据表示标准(External Data Representation)。XDR提供了一种与体系结构无关的表示数据,解决了数据字节排序的差异、数据字节大小、数据表示和数据对准的方式。使用XDR的应用程序,可以在异构硬件系统上交换数据。由于RData文件采用XDR编码,了解XDR的基本规则对于解析至关重要:
- 字节序:XDR规定所有多字节数值类型(如整数、浮点数)都采用大端字节序(Big-Endian)存储。这意味着最高有效字节(MSB)位于最低地址,最低有效字节(LSB)位于最高地址。这与常见的x86/x64架构的小端字节序相反。在VB.NET中,可以使用 BitConverter.IsLittleEndian 检测当前字节序,并在必要时使用 Array.Reverse 或 BinaryPrimitives 类的方法来转换字节序。
- 对齐:XDR要求数据按4字节边界对齐。这意味着每个数据项的起始地址都是4的倍数。如果某个数据项的字节长度不是4的倍数,其后会填充0到3个零字节,使下一个数据项的地址对齐到4字节边界。在解析时,需要跳过这些填充字节。例如,一个长度为3的字符串数据后面会跟1个填充字节,以保证后续数据从4字节边界开始。
-
数据类型表示:XDR定义了基本数据类型的二进制表示,例如:
- 整数:32位有符号整数,采用补码表示,范围在-2147483648到2147483647。在文件中占4字节,大端序。
- 无符号整数:32位无符号整数,范围在0到4294967295。在文件中占4字节,大端序。
- 布尔值:XDR没有单独的布尔类型,通常用整数表示,0表示假,非0表示真。
- 浮点数:32位单精度浮点数(IEEE 754标准)和64位双精度浮点数。RData文件中通常使用双精度浮点数(REALSXP),占8字节,大端序。
- 字符串:字符串在XDR中表示为长度前缀的字符序列。首先存储一个4字节的整数表示字符串的字节长度,然后存储字符串的字节内容。字符串通常以空字符结尾,但在XDR中并不强制要求。在R中,字符串采用UTF-8或Latin1编码,具体取决于环境和设置。
对于RData数据集,由于我们可能会将RData放在对应的R package中发布给其他人使用。所以为了达到数据编码在不同的计算机硬件和操作系统平台之间的兼容性,在RData数据集中广泛的使用XDR编码方案来解决平台之间的兼容性问题。在进行RData数据文件的读取操作的时候,会需要使用到两个重要的数据类型的XDR解码函数:
对于进行32位整形数的解码操作,我们可以通过VB.NET基础框架中的实现。UnpackInteger函数的底层实现是依赖于一个名字叫做DecodeInt32的函数来完成:XDRParser.UnpackInteger
''' <summary>
''' Decodes the Int32.
''' http://tools.ietf.org/html/rfc4506#section-4.1
''' </summary>
Public Function DecodeInt32(r As IByteReader) As Integer
If r.EndOfStream Then
' Return 0
Throw New InvalidProgramException
Else
' 20211203
' default in VB.NET is byte shift
' should be convert to integer at first
Dim H18 = CInt(r.Read) << &H18
Dim H10 = CInt(r.Read) << &H10
Dim H8 = CInt(r.Read) << &H8
Dim H0 = CInt(r.Read)
Return H18 Or H10 Or H8 Or H0
End If
End Function
对于双精度类型,我们可以通过函数来完成解码。与Integer不同的是,Double类型浮点数的解码是构建在64位整型数的基础上完成的。所以我们可以有下面所示的底层解码代码:XDRParser.UnpackDouble
''' <summary>
''' Decodes the Double.
''' http://tools.ietf.org/html/rfc4506#section-4.7
''' </summary>
Public Function DecodeDouble(r As IByteReader) As Double
Dim num As Long = Xdr.XdrEncoding.DecodeInt64(r)
Return unsafeDouble(num)
End Function
''' <summary>
''' Decodes the Int64.
''' http://tools.ietf.org/html/rfc4506#section-4.5
''' </summary>
Public Function DecodeInt64(r As IByteReader) As Long
Return (CLng(r.Read()) << 56) _
Or (CLng(r.Read()) << 48) _
Or (CLng(r.Read()) << 40) _
Or (CLng(r.Read()) << 32) _
Or (CLng(r.Read()) << 24) _
Or (CLng(r.Read()) << 16) _
Or (CLng(r.Read()) << 8) _
Or (CLng(r.Read()))
End Function
Private Function unsafeDouble(x As Long) As Double
Dim bytes = BitConverter.GetBytes(x)
Dim dbl As Double = BitConverter.ToDouble(bytes, 0)
Return dbl
End Function
在VisualBasic中解析RData数据集
RData在假设大家都已经明白了上面所讲解的进行RData数据集解析所必须的一些基础知识之后,我们下面就可以开始学习对RData数据文件的详细解析代码的实现了。
文件级别的解析代码
对于RData数据集而言,可能存在有bz2,gzip,xz这三种文件压缩格式。也可能是没有进行压缩的,直接暴露在外面的RData裸数据文件。所以在数据集读取的最开始阶段,我们就需要根据数据流之中的幻数头进行文件压缩类型的判断。下面的代码给出了在RData之中的文件类型的幻数头常数:
ReadOnly magic_dict As New Dictionary(Of FileTypes, Byte()) From {
{FileTypes.bzip2, bytes("\x42\x5a\x68")},
{FileTypes.gzip, bytes("\x1f\x8b")},
{FileTypes.xz, bytes("\xFD7zXZ\x00")},
{FileTypes.rdata_binary_v2, bytes("RDX2\n")},
{FileTypes.rdata_binary_v3, bytes("RDX3\n")}
}
根据不同的幻数检查结果,我们使用对应的数据压缩格式进行数据流的解压缩之后再进行XDR文件读取即可。具体的解压缩代码在这里不在做赘述。
在完成了最外层的数据流解压缩操作之后,整个RData数据集就直接暴露在外面了。现在我们再根据RData格式类型的幻数头进行对应版本的RData读取模块的调用。进行RData格式的幻数头的检查与之前的文件压缩幻数头检查的操作是一样的。下面的代码给出了在RData之中的文件格式的幻数常数定义:
ReadOnly format_dict As New Dictionary(Of RdataFormats, Byte()) From {
{RdataFormats.XDR, bytes("X\n")},
{RdataFormats.ASCII, bytes("A\n")},
{RdataFormats.binary, bytes("B\n")}
}
可以看得到,在RData之中存在有三个版本的编码格式:默认的XDR编码格式,ASCII编码格式以及二进制编码格式。在这里我们主要学习的是对基于XDR编码格式的RData文件读取的代码。
文件元数据读取
在RData文件数据之中,仅存在有两个元数据信息:R环境版本以及文本编码。我们在进行读取的时候,需要先读取版本信息,在读取文本编码信息,最后进行具体的R数据的读取操作。对应的读取函数如下所示:
''' <summary>
''' Parse the versions header.
''' </summary>
''' <returns></returns>
Public Function parse_versions() As RVersions
Dim format_version = parse_int()
Dim r_version = parse_int()
Dim minimum_r_version = parse_int()
Static supportedVer As New Index(Of Integer) From {2, 3}
If Not format_version Like supportedVer Then
Throw New NotImplementedException($"Format version {format_version} unsupported")
End If
Return New RVersions With {
.format = format_version,
.serialized = r_version,
.minimum = minimum_r_version
}
End Function
''' <summary>
''' Parse the versions header.
''' </summary>
''' <param name="versions"></param>
''' <returns></returns>
Public Function parse_extra_info(versions As RVersions) As RExtraInfo
Dim encoding As String = Nothing
Dim encoding_len As Integer
If versions.format >= 3 Then
encoding_len = parse_int()
encoding = parse_string(encoding_len).decode(Encodings.ASCII)
End If
Dim extract_info = New RExtraInfo With {.encoding = encoding}
Return extract_info
End Function
可以看得到,RData版本号是由三个Integer数构成的。进行Integer数读取的parse_int函数就是我们进行Integer值的XDR解码函数调用。文本字符串编码信息,则是以字符串长度作为前缀的ASCII字符串的形式保存在RData文件之中。
解析RObject对象
RObject前面我们提到,在RData数据集之中,所有的数据都是以二叉树加链表的形式存储在一个二进制文件之中。在我们所读取的链表中,数据单元被称作为RObject对象。下面所展示的代码,为大家展示了RData之中的基本单元里面具体包含有哪些数据信息:
''' <summary>
''' Representation of a R object.
''' </summary>
Public Class RObject
Public Property info As RObjectInfo
Public Property value As RList
Public Property attributes As RObject
Public Property tag As RObject
Public Property referenced_object As RObject
End Class
Public Class RList
Public Property CAR As RObject
Public Property CDR As RObject
Public Property data As Array
End Class
''' <summary>
''' Internal attributes of a R object.
''' </summary>
Public Class RObjectInfo
Public type As RObjectType
Public [object] As Boolean
Public attributes As Boolean
Public tag As Boolean
Public gp As Integer
Public reference As Integer
End Class
从上面的对象定义之中可以看得到,RObject对象就是在RData数据集之中的基本数据单元。无论是数据对象本身还是数据对象其所具有的属性,标签等元数据信息,都是RObject对象。对于的实现,则是基于一个名称为RList的数据类型来实现的。链表+二叉树结构
在RList之中,CAR属性或者data属性为在链表中当前的数据节点中的数据存储区。其中data属性用于存储向量数据,而CAR属性则用于存储组合类型的复杂数据关系,例如list,dataframe等结构化的非基础类型数据元件之间的关联信息。CDR则是链表之中的链接信息,通过CDR属性我们可以从当前节点跳转到下一个RObject数据节点进行数据读取操作。因为在RList之中,仅存在有CAR和CDR这两个属性进行链表的延申,所以基于这两个属性,由产生了一个二叉树的结构。为大家总结的进行理解RData文件格式的重点:
- CAR为当前的符号对象的值存储链,读取当前的符号值所有的数据都可以通过读取CAR路径来实现
- CDR则链接到了下一个符号对象的数据存储链,如果要读取其他的符号数据,则必须要通过CDR路径来实现
就这样,在RData之中,基于上面所展示的RObject的结构,我们就可以在二进制数据流这样子的一维线性空间之中描述出list,dataframe这样子的结构化的多维度数据了。链表+二叉树
RObject元数据读取
在学习RData文件格式的时候,最让我惊讶的是在RData之中对数据空间的利用最大化。例如对于上面所示的元数据对象,看着里面那么多的属性信息,大家一般会首先想到的是通过多个integer或者bytes数据进行信息的存储。但是在RData之中并不是这样浪费空间,RData之中通过非常精妙的利用Integer的32为比特空间进行上面所展示的元数据信息的存储。是的,仅使用一个Integer数就可以保存上面的所有元数据信息:RObjectInfo
''' <summary>
''' Parse the internal information of an object.
''' </summary>
''' <param name="info_int"></param>
''' <returns></returns>
Public Function parse_r_object_info(info_int As Integer) As RObjectInfo
Dim type_exp As RObjectType = bits(info_int, 0, 8)
Dim reference = 0
Dim object_flag As Boolean
Dim attributes As Boolean
Dim tag As Boolean
Dim gp As Integer
If is_special_r_object_type(type_exp) Then
object_flag = False
attributes = False
tag = False
gp = 0
Else
object_flag = CBool(bits(info_int, 8, 9))
attributes = CBool(bits(info_int, 9, 10))
tag = CBool(bits(info_int, 10, 11))
gp = bits(info_int, 12, 28)
End If
If type_exp = RObjectType.REF Then
reference = bits(info_int, 8, 32)
End If
Return New RObjectInfo With {
.type = type_exp,
.[object] = object_flag,
.attributes = attributes,
.tag = tag,
.gp = gp,
.reference = reference
}
End Function
从上面的代码中可以看得到,通过对不同的比特位的运算,我们就可以存储上面所展示的所有的元数据信息了。在RData格式中为什么会进行这样子的编码操作呢?我总结了一下,大致可能是下面的一个原因:必须要节省数据空间!
因为对于R环境而言,一般用RData文件保存大量的科学计算数据结果。因为RData是通过链表的形式来组成的,而在这个链表之中,一般会存在有非常多的节点,这些Robject节点,有的用于存储真正的数据,有的用于存储元数据信息,并且每一个RObject节点,都有其各自的RObjectInfo元数据信息。所以必须要尽量的利用上每一个比特的空间。如果直接使用多个Byte或者Integer来存储元数据信息的话,随着所需要保存的数据量的上升,RData数据集里面的RObject对象的数量也会随着增加非常多,这样子光元数据信息的存储都将会占用非常大的空间。所以基于上面的原因,在RData之中,必须要基于比特位的运算充分的将所有数据空间利用起来以节省内存空间和硬盘空间。RObject
RObject对象值类型
在下面的代码中定义了RObject所有可能的值类型:
' r object
NIL = 0, SYM = 1, LIST = 2, CLO = 3, ENV = 4, PROM = 5, LANG = 6, SPECIAL = 7, BUILTIN = 8
' element vector
CHAR = 9, LGL = 10, INT = 13, REAL = 14, CPLX = 15, STR = 16
' r language
DOT = 17, ANY = 18, VEC = 19, EXPR = 20, BCODE = 21, EXTPTR = 22, WEAKREF = 23, RAW = 24, S4 = 25
' alternative flags
ALTREP = 238, EMPTYENV = 242, GLOBALENV = 253, NILVALUE = 254, REF = 255
通过上面的代码对RObjectInfo元数据的读取,就可以得到对应的RObject的数据类型了。根据不同的数据类型,我们就需要不同的数据对象读取代码进行对应的读取操作。在这里,我仅仅为大家讲解RData之中的数据结构的读取以及向量数据的读取操作的代码。其他的数据类型的RObject的读取操作,大家可以阅读R#的源代码文件。链表+二叉树
链表结构的读取
在RData之中,目前只有LIST类型和LANG类型为链表结构。进行链表结构的数据,其实我们就只需要进行递归的进行RObject对象读取函数的调用即可。在读取的时候我们分别读取CAR和CDR节点的数据即可完成链表的构建以及二叉树的读取操作:
' parse_R_object
tag = Nothing
If info.attributes Then
attributes = parse_R_object(reference_list)
attributes_read = True
ElseIf info.tag Then
tag = parse_R_object(reference_list)
tag_read = True
End If
' Read CAR and CDR
' RData linked list
Dim car As RObject = parse_R_object(reference_list)
Dim cdr As RObject = parse_R_object(reference_list)
value = New RList With {.CAR = car, .CDR = cdr}
从上面的代码中我们可以看得到,我们在parse_R_object函数之中,递归的调用函数其自身进行CAR和CDR节点的读取,既可以构建出的结构。链表+二叉树
向量的读取
在RData之中的向量数据,我们可以通过一个通用的泛型函数进行相应的数据读取操作:
Private Function parseVector(Of T)(parse As Func(Of T)) As Array
Dim length As Integer = parse_int()
Dim value As T() = New T(length - 1) {}
For i As Integer = 0 To length - 1
value(i) = parse()
Next
Return value
End Function
对于RData之中的向量,一般是以一个用于表示向量长度的32位整数作为整个数据区的起始,然后向量元素值依次线性排布在后面。对于RData之中的向量,一般只有7种数据类型可以以向量的形式进行存储:
If info.type = RObjectType.LGL Then
value = parseVector(AddressOf parse_bool)
ElseIf info.type = RObjectType.INT Then
value = parseVector(AddressOf parse_int)
ElseIf info.type = RObjectType.REAL Then
value = parseVector(AddressOf parse_double)
ElseIf info.type = RObjectType.CPLX Then
value = parseVector(AddressOf parse_complex)
ElseIf info.type Like objType3 Then
value = parseVector(Function() parse_R_object(reference_list))
上面所示的7种数据类型分别为:LGL逻辑值类型,INT整型数,REAL双精度浮点数,CPLX两个双精度浮点数组成的复数,STR字符串类型,EXPR表达式类型以及VEC数据类型。请注意,在这里的VEC类型并不是vector向量,而是元素数组的概念。例如,在list数据结构之中,元素值就是以数组类型存储的;在dataframe数据结构之中,列数据值也是以数组类型存储的。所以VEC在这里指的是list或者dataframe这类高维度数据的数据存储,而非向量这类基元类型的基础数据存储。
对于完整的RObject解析代码,大家可以阅读【RData/Reader.vb】源代码文件。
至此,整个RData文件已经被完全读取了,将我们所读取的元数据加RObject链表二叉树数据组装在一起,就可以得到RData对象数据了。
''' <summary>
''' Parse all the file.
''' </summary>
''' <returns></returns>
Public Function parse_all() As RData
Dim versions As RVersions = parse_versions()
Dim extra_info As RExtraInfo = parse_extra_info(versions)
Dim obj As RObject = parse_R_object()
Return New RData With {
.versions = versions,
.extra = extra_info,
.[Object] = obj
}
End Function
VisualBasic代码用例
VisualBasic在VisualBasic程序之中使用对应的API进行RData文件的读取操作非常的简单,只需要引用代码库项目之后,导入对应的数据集处理相关的命令空间,然后使用ParseData函数即可读取R语言的Rda文件:RData
Imports SMRUCC.Rsharp.RDataSet
Using buffer As Stream = file.Open(FileMode.Open, doClear:=False, [readOnly]:=True)
Dim obj As Struct.RData = Reader.ParseData(buffer)
' blabla
End Using
可以看得见,我们通过执行上面的代码,成功的将目标rda数据文件进行读取,并且文件里面包含有一个变量名为的对象:test_dataframe

- limma程序包在RNA-seq差异表达分析中的数学算法原理与实现详解 - 2025年12月16日
- 零分布原理及其在生物信息学中的应用 - 2025年12月15日
- 宏基因组测序数据基因丰度估算方法理论 - 2025年12月8日


One response
[…] 从上一篇博客文章之中我们比较下详细的了解了RData数据文件的文件格式以及对应的读取操作。在这篇文章之中我们来了解如何基于我们通过对RData文件读取操作所获取得到的链表数据进行反序列化操作,将R环境之中的数据集串流加载到下游的R#数据分析环境之中。 […]