科学数据存储
科学数据存储
有许多存储数据的方法,特别是以数组形式出现的数据。有很多人在电子表格中存储数据,然后将它们导出到带有逗号或制表符的 ascii 文件中,并希望其他人(或由他们自己编写的其他程序)再次读入这些数据,这样一个过程在以下几个方面的浪费:
- 一个数字的 ascii 表示法要比内部的二进制表示法占用更多的空间,理想情况下,你希望一个文件和内存中的表示一样紧凑。
- ascii 的转换很慢;它也可能导致精度的损失。
由于这样的原因,最好能有一种基于二进制存储的文件格式。还有一些对一个有用的文件格式的要求:
- 由于二进制存储在不同的平台之间会有差异,所以一个好的文件格式是与平台无关的。例如,这将防止混淆大端字节序和小端字节序,以及32位和64位浮点数的约定。
- 应用程序数据可能是异质的,包括整数、字符和浮点数据,理想情况下,所有这些数据应该被存储在一起。
- 应用数据也是有结构的,这种结构应该反映在存储的形式中。
- 一个文件格式最好是自带文档的,如果文件本身能告诉你哪些存储的数字是矩阵,哪些是向量,以及这些对象的大小是什么难道不是一件很好的事吗?
本教程将介绍 HDF5 库,它可以满足这些要求。HDF5 是一个庞大而复杂的库,所以本教程将只涉及到基础知识。欲知更多信息,请查阅http://www.hdfgroup.org/HDF5/ 当你做这个教程的时候,请保持你的浏览器在http://www.hdfgroup.org/HDF5/doc/ 或 http://www.hdfgroup.org/HDF5/RM/RM_H5Front.html 来了解这些例程的确切语法。
HDF5 简介
如上所述,HDF5 是一种与机器无关的文件格式,并且是自我记录的。每个 HDF5 文件的设置就像一个目录树,有子目录和包含实际数据的叶子结点。这意味着可以通过参考文件的名称,而不是文件中的位置来找到数据。本节中,你将学习如何编写向 HDF5 文件写入和从 HDF5 文件中读取的程序。为了检查文件是否符合你的目的,你可以在命令行中使用 h5dump 工具。
HDF5 格式与旧版本的 HDF4 并不兼容,后者已不再开发。由于历史原因,你仍然可以碰到有人使用 HDF4 。本教程是基于 HDF5 的1.6版本。在当前的1.8版本中,一些接口发生了变化;为了在1.8版本的软件中使用1.6版本的API,请在你的编译行中添加一个 -DH5_USE_16_API 标志。
许多 HDF5 例程是关于创建对象的:文件句柄、数据集中的成员等等。一般语法是:
hid_t h_id;
h_id = H5Xsomething(...);
创建对象的失败是由一个负的返回参数表示的,所以最好是创建一个包含 myh5defs.h 的文件。
#include "hdf5.h"
#define H5REPORT(e) \
{if (e<0) {printf("\nHDF5 error on line %d\n\n",__LINE__); \
return e;}}
并将此作为:
#include "myh5defs.h"
hid_t h_id;
h_id = H5Xsomething(...); H5REPORT(h_id);
创建一个文件
首先,我们需要创建一个 HDF5 文件。
hid_t file_id;
herr_t status;
file_id = H5Fcreate( filename, ... );
...
status = H5Fclose(file_id);
这个文件将是一些数据项的容器,像目录树一样组织。
练习:通过编译和运行下面的 create.c 例子创建一个 HDF5 文件。
预期结果:应该会创建一个 file.h5 文件。
注意事项:请确保添加 HDF5 的包含目录和库目录。
cc -c create.c -I. -I/opt/local/include
然后
cc -o create create.o -L/opt/local/lib -lhdf5.
include 和 lib 目录将取决于系统。
/*
* File: create.c
* Author: Victor Eijkhout
*/
#include "myh5defs.h"
#define FILE "file.h5"
main() {
hid_t file_id; /* file identifier */
herr_t status;
/* Create a new file using default properties. */
file_id = H5Fcreate(FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
H5REPORT(file_id);
/* Terminate access to the file. */
status = H5Fclose(file_id);
}
你可以在命令行上显示创建的文件:
%% h5dump file.h5
HDF5 "file.h5" {
GROUP "/" {
}
}
请注意,一个空文件只对应于将容纳数据的目录树的根。
数据集
接下来我们创建一个数据集,在这个例子中是一个二维网格。为了描述这个,我们首先需要构建一个数据空间:
dims[0] = 4; dims[1] = 6;
dataspace_id = H5Screate_simple(2, dims, NULL);
dataset_id = H5Dcreate(file_id, "/dset", dataspace_id, .... );
....
status = H5Dclose(dataset_id);
status = H5Sclose(dataspace_id);
注意,数据集和数据空间需要像文件一样被关闭。
练习:通过编译和运行下面的 dataset.c 代码创建一个数据集。
预期结果:这将创建一个 dset.h 文件,可以用 h5dump 显示。
/*
* File: dataset.c
* Author: Victor Eijkhout
*/
#include "myh5defs.h"
#define FILE "dset.h5"
main() {
hid_t file_id, dataset_id, dataspace_id; /* identifiers */
hsize_t dims[2];
herr_t status;
/* Create a new file using default properties. */
file_id = H5Fcreate(FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
/* Create the data space for the dataset. */
dims[0] = 4;
dims[1] = 6;
dataspace_id = H5Screate_simple(2, dims, NULL);
/* Create the dataset. */
dataset_id = H5Dcreate(file_id, "/dset",H5T_NATIVE_INT,dataspace_id,H5P_DEFAULT);
/*H5T_STD_I32BE*/
/* End access to the dataset and release resources used by it. */
status = H5Dclose(dataset_id);
/* Terminate access to the data space. */
status = H5Sclose(dataspace_id);
/* Close the file. */
status = H5Fclose(file_id);
}
我们再次在线查看创建的文件:
%% h5dump dset.h5
HDF5 "dset.h5" {
GROUP "/" {
DATASET "dset" {
DATATYPE H5T_STD_I32BE
DATASPACE SIMPLE { ( 4, 6 ) / ( 4, 6 ) }
DATA {
(0,0): 0, 0, 0, 0, 0, 0,
(1,0): 0, 0, 0, 0, 0, 0,
(2,0): 0, 0, 0, 0, 0, 0,
(3,0): 0, 0, 0, 0, 0, 0
}
}
}
}
数据文件包含诸如你存储的数组的大小等信息,不过你可能还想添加相关的标量信息。例如,如果数组是一个程序的输出,你可以记录用什么输入参数生成的。
parmspace = H5Screate(H5S_SCALAR);
parm_id = H5Dcreate(file_id,"/parm",H5T_NATIVE_INT,parmspace,H5P_DEFAULT);
练习:通过编译和运行下面的 parmwrite.c 代码,在 HDF5 文件中添加一个标量数据空间。
预期结果:一个新的 wdset.h5 文件被创建。
/*
* File: parmdataset.c
* Author: Victor Eijkhout
*/
#include "myh5defs.h"
#define FILE "pdset.h5"
main() {
hid_t file_id, dataset_id, dataspace_id; /* identifiers */
hid_t parm_id,parmspace;
hsize_t dims[2];
herr_t status;
/* Create a new file using default properties. */
file_id = H5Fcreate(FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
/* Create the data space for the dataset. */
dims[0] = 4;
dims[1] = 6;
dataspace_id = H5Screate_simple(2, dims, NULL);
/* Create the dataset. */
dataset_id = H5Dcreate(file_id, "/dset", H5T_STD_I32BE, dataspace_id, H5P_DEFAULT);
/* Add a descriptive parameter */
parmspace = H5Screate(H5S_SCALAR);
parm_id = H5Dcreate(file_id,"/parm",H5T_NATIVE_INT,parmspace,H5P_DEFAULT);
/* End access to the dataset and release resources used by it. */
status = H5Dclose(dataset_id);
status = H5Dclose(parm_id);
/* Terminate access to the data space. */
status = H5Sclose(dataspace_id);
status = H5Sclose(parmspace);
/* Close the file. */
status = H5Fclose(file_id);
}
%% h5dump wdset.h5
HDF5 "wdset.h5" {
GROUP "/" {
DATASET "dset" {
DATATYPE H5T_IEEE_F64LE
DATASPACE SIMPLE { ( 4, 6 ) / ( 4, 6 ) }
DATA {
(0,0): 0.5, 1.5, 2.5, 3.5, 4.5, 5.5,
(1,0): 6.5, 7.5, 8.5, 9.5, 10.5, 11.5,
(2,0): 12.5, 13.5, 14.5, 15.5, 16.5, 17.5,
(3,0): 18.5, 19.5, 20.5, 21.5, 22.5, 23.5
}
}
DATASET "parm" {
DATATYPE H5T_STD_I32LE
DATASPACE SCALAR
DATA {
(0): 37
}
}
}
}
写入数据
你创建的数据集分配了 HDF5 文件中的空间,现在你需要把实际的数据放在里面。这就是用 H5Dwrite 调用完成
/* Write floating point data */
for (i=0; i<24; i++) data[i] = i+.5;
status = H5Dwrite(dataset,H5T_NATIVE_DOUBLE,H5S_ALL,H5S_ALL,H5P_DEFAULT,data);
/* write parameter value */
parm = 37;
status = H5Dwrite(parmset,H5T_NATIVE_INT,H5S_ALL,H5S_ALL,H5P_DEFAULT,&parm);
/*
* File: parmwrite.c
* Author: Victor Eijkhout
*/
#include "myh5defs.h"
#define FILE "wdset.h5"
main() {
hid_t file_id, dataset, dataspace; /* identifiers */
hid_t parmset,parmspace;
hsize_t dims[2];
herr_t status;
double data[24]; int i,parm;
/* Create a new file using default properties. */
file_id = H5Fcreate(FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
/* Create the dataset. */
dims[0] = 4; dims[1] = 6;
dataspace = H5Screate_simple(2, dims, NULL);
dataset = H5Dcreate(file_id, "/dset", H5T_NATIVE_DOUBLE, dataspace, H5P_DEFAULT);
/* Add a descriptive parameter */
parmspace = H5Screate(H5S_SCALAR);
parmset = H5Dcreate(file_id,"/parm",H5T_NATIVE_INT,parmspace,H5P_DEFAULT);
/* Write data to file */
for (i=0; i<24; i++) data[i] = i+.5;
status = H5Dwrite(dataset,H5T_NATIVE_DOUBLE,H5S_ALL,H5S_ALL,H5P_DEFAULT,data); H5REPORT(status);
/* write parameter value */
parm = 37;
status = H5Dwrite(parmset,H5T_NATIVE_INT,H5S_ALL,H5S_ALL,H5P_DEFAULT,&parm); H5REPORT(status);
/* End access to the dataset and release resources used by it. */
status = H5Dclose(dataset);
status = H5Dclose(parmset);
/* Terminate access to the data space. */
status = H5Sclose(dataspace);
status = H5Sclose(parmspace);
/* Close the file. */
status = H5Fclose(file_id);
}
%% h5dump wdset.h5
HDF5 "wdset.h5" {
GROUP "/" {
DATASET "dset" {
DATATYPE H5T_IEEE_F64LE
DATASPACE SIMPLE { ( 4, 6 ) / ( 4, 6 ) }
DATA {
(0,0): 0.5, 1.5, 2.5, 3.5, 4.5, 5.5,
(1,0): 6.5, 7.5, 8.5, 9.5, 10.5, 11.5,
(2,0): 12.5, 13.5, 14.5, 15.5, 16.5, 17.5,
(3,0): 18.5, 19.5, 20.5, 21.5, 22.5, 23.5
}
}
DATASET "parm" {
DATATYPE H5T_STD_I32LE
DATASPACE SCALAR
DATA {
(0): 37
}
}
}
}
如果你仔细看一下源代码和备份文件系统,你会发现数据类型被声明为 “本地”,但是呈现为LE 。“本地”声明使数据类型的行为与内置的 C 或 Fortran 数据类型相似,另外,你也可以明确指出数据是小端字节序还是大端字节序。这些术语描述了一个数据项的字节在内存中的排序方式。正如你在备份文件系统输出中所看到的那样,大多数架构都使用小端字节序(little endian)。但值得注意的是,IBM 使用的是大端字节序。
读取
现在我们有了一个包含一些数据的文件,我们可以从该文件中读取数据。这些基本命令是
h5file = H5Fopen( .... )
....
H5Dread( dataset, .... data .... )
其中 H5Dread 命令的参数与相应的 H5Dwrite 相同。
练习:通过编译和运行下面的 allread.c 例子,从你在前面的练习中创建的 wdset.h5 文件中读取数据。
预期结果:运行可执行文件 allread 将输出参数的值 37 和数组的(1,2)数据点的值8.5。
注意事项:确保你运行 parmwrite 来创建输入文件。
/*
* File: allread.c
* Author: Victor Eijkhout
*/
#include "myh5defs.h"
#define FILE "wdset.h5"
main() {
hid_t file_id, dataset, parmset;
herr_t status;
double data[24]; int parm;
/* Open an existing file */
file_id = H5Fopen(FILE, H5F_ACC_RDONLY, H5P_DEFAULT);
H5REPORT(file_id);
/* Locate the datasets. */
dataset = H5Dopen(file_id, "/dset"); H5REPORT(dataset);
parmset = H5Dopen(file_id,"/parm"); H5REPORT(parmset);
/* Read data back */
status = H5Dread(parmset,H5T_NATIVE_INT,H5S_ALL,H5S_ALL,H5P_DEFAULT,&parm); H5REPORT(status);
printf("parameter value: %d\n",parm);
status = H5Dread(dataset,H5T_NATIVE_DOUBLE,H5S_ALL,H5S_ALL,H5P_DEFAULT,data); H5REPORT(status);
printf("arbitrary data point [1,2]: %e\n",data[1*6+2]);
/* Terminate access to the datasets */
status = H5Dclose(dataset); H5REPORT(status);
status = H5Dclose(parmset); H5REPORT(status);
/* Close the file. */
status = H5Fclose(file_id);
}
%% ./allread
parameter value: 37
arbitrary data point [1,2]: 8.500000e+00c