ODC库使用:数据解码
代码示例及API使用Fortran接口进行说明,C相关接口可查阅官方文档。
数据解码
解码(Decoder)过程就是从ODB-2消息流(常为文件载体)解码到计算机内存中。编码(encoder)是解码的逆过程。
ODB-2数据解码在API层面涉及三个类对象(ODC库使用C++实现,使用面向对象设计),涉及到从文件操作底层到解码高层层级。
- Reader对象
Reader对象负责ODB-2数据流的底层资源控制,比如ODB-2文件打开、关闭。
type(odc_reader) :: reader !定义Reader对象
rc = reader%open_path("imaginary/path.odb") !打开ODB-2文件
...
rc = reader%close() ! 关闭odb-2文件,释放资源
- Frame对象
Frame对象提供ODB-2消息流的连续数据的一块(chunk)切片视窗。该数据块必须拥有相同的列结构(即列的数目、名称及关联数据类型)(这就是frame的定义,即ODB-2处理数据单元是一个frame)。
Frame对象在不用解码数据(data)情况下可获取元数据header信息(因为header和data是分开的),包括frame表格的行数和列信息。
type(odc_frame) :: frame ! 定义一个frame对象
logical, parameter :: aggregated = .true. !聚合兼容数据为一个逻辑frame
integer, parameter :: max_aggregated_rows = 1000000 ! 如果打开聚合功能,设置聚合最大行数
rc = frame%initialise(reader) ! 从reader对象中初始化frame对象
rc = frame%next(aggregated, max_aggregated_rows) !进入下一个frame中
获取header信息
rc = frame%row_count(row_count) ! 获取当前frame的行数
rc = frame%column_count(column_count) ! 获取当前frame的列数
获取column属性
do col = 1, column_count ! 对每一列迭代
rc = frame%column_attributes(col, name, type, element_size, bitfield_count=bitfield_count) ! 从当前frame中获取column属性,column索引从1开始,获取包括名称、数据类型、列所占字节数、bitfield flags数目
rc = odc_column_type_name(type, type_name) !将整数表示的type转换成可读的类型名称,字符串形式
print *, "Column ", col
print *, " name: ", name
print *, " type: ", type_name
print *, " size: ", element_size
if (type == ODC_BITFIELD) then
do bf = 1, bitfield_count
rc = frame%bitfield_attributes(col, bf, bf_name, bf_offset, bf_size) !获取每个flag属性
print *, " bitfield ", bf
print *, " name: ", bf_name
print *, " offset: ", bf_offset
print *, " nbits: ", bf_size
end do
end if
end do
需要注意的是如果列数据类型有bitfield
,要特殊处理,获取flag的offset/size信息。
- Decoder对象
Decoder对象指定解码操作如何实施。它配置了要解码的列集(column set)和要解码的数据在内存中的布局。
通常许多配置可以通过查询上一层的Frame对象的缺省值进行填充,不需要额外设置。在这些情况下所有列会被解码,内存布局或者是行主元或者列主元。
type(odc_decoder) :: decoder ! 定义一个decoder对象
integer(8), target :: rows_decoded
real(8), pointer :: data(:,:)
logical :: column_major
rc = decoder%initialise() ! 初始化decoder
rc = decoder%defaults_from_frame(frame) !从frame对象配置decoder参数
rc = decoder%decode(frame, rows_decoded) ! 解码frame中数据到连续数组中,rows_decoded返回解码行数
print *, "Decoded ", rows_decoded, " rows"
rc = decoder%data(data, column_major) ! 取解码数据到数组中,column_major返回是否使用列主元内存布局
print *, "Decoded into a 2D array:"
print *, " First element location: ", loc(data(1,1))
print *, " Table width (columns): ", size(data, 2)
print *, " Table height (rows): ", size(data, 1)
print *, " Column major: ", merge(" true", "false", column_major)
rc = decoder%free()
如果多个frames的列集和内存布局相同,可以复用decoder实例。
内存布局
前面提到的内存布局列主元和行主元代表一条观测记录列属性之间在内存中是否连续。
- Row-major layout
列主元内存布局:相同列连续元素在内存中间隔一个行宽度。
Fortran数组在内存中是列主元,而C是行主元布局。因此,在不同编程语言下要注意内存布局和语言的对应关系。
比如ODB-2消息是行主元布局,使用Fortran解码,Fortran数组最内维在内存中是连续的,因此声明的数组应该将ncols放在最内维,即data(ncols,nrows)
。
real(8), target :: data(ncols, nrows)
logical, parameter :: column_major = .false.
rc = decoder%set_data(data, column_major) ! 设置解码的数组,行主元解码
- Column-major layout
为了支持C和Fortran 2D数组索引,在列主元模式下数据元素大小总是64位。对于超过8字节宽度的字符列会导致字符串在内存中跨越多列(因为一列放不下所有字符串,同时列主元导致行元素之间内存不连续)。
real(8), target :: data(nrows, ncols)
logical, parameter :: column_major = .true.
rc = decoder%set_data(data, column_major)
- Custom layout
更一般化的是使用定制布局,对于周期性内存布局可以显式指定每列:第一个元素的内存地址、每个元素size、每个元素之间间隔以及解码最大行数。
Properties
ODB-2格式允许对frame增加注释,使用字符串键值对形式。这些注释属性可以通过Frame
对象访问。
integer :: nproperties, idx
character(:), allocatable, target :: key, val
logical :: exists
! Get number of properties encoded in the frame
rc = frame%properties_count(nproperties)
do idx = 1, nproperties
! Get property key and value by its index
frame%property_idx(idx, key, val)
print *, " Property: ", key, " => ", val
end do
! Or, get property value by its key
rc = frame%property('my_key', val, exists)
if (exists) print *, " Property: my_key => ", val

