HDF5_CPP使用教程

前言

由于工作原因接触到HDF5这种文件格式,这是一个无关平台的文件格式,提供多个语言的支持,有c,java,c++等。本人从事Qt,所以主要研究c++的库的使用方法。这个文件结构有点类似于一个完整的文件目录:顶层是根目录(”/“),然后可以添加新的目录,HDF5称之为Group,目录里面可以继续添加子目录,或者文件(DataSet)和属性(Attribute),这两种都可以通过数据空间(DataSpace)存储数据,数据类型可以是整形,浮点型,字符等。通过以上介绍大概就能知道这种文件的可扩展性很强。库文件可以从官网上直接下载对应的版本,但是只要vs的vc++编译的版本,如果使用mingw编译器需要编译源码,但是我尝试了一下,bug太多,最后放弃了,也没有必要浪费时间在库的编译上。从官网上下载的安装包安装之后,根目录下include目录是头文件,lib目录是静态链接库目录,其中hdf5.lib是c的,hdf5_cpp.lib是c++的库。

使用示例

根据工作需要,我使用HDF5文件创建一个树形结构,然后显示到界面上,然后做一些简单的交互。

Pro文件配置

H5_BUILD_AS_DYNAMIC_LIB是HDF5的一个预编译宏,不添加这个使用HDF5库会提示未定义的方法等错误。这个库在debug模式下也是经常崩溃,应该是这个库是release模式,所以在pro文件里面只配置了release模式。链接库我使用了c++的和c的,这是因为在后续的实践中发现,c的库函数有很多没有移植到c++中,只能采取c的函数进行替代。

创建HDF5文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
const int RANK = 2; //定义数组的维度
const int M = 10;
const int N = 10;

//准备要存储的数据
char *data = new char[M*N];
char *tmp=NULL;
for(int i =0; i<M; ++i){
for(int j =0; j<N; ++j){
tmp = data + i*N +j;
*tmp = 80+i+j;
}
}

try
{
// Turn off the auto-printing when failure occurs so that we can
// handle the errors appropriately

Exception::dontPrint();
//创建文件
H5File file(filename, H5F_ACC_TRUNC);
//创建第一层 group section
Group group_Section0(file.createGroup("Section0"));
group_Section0.setComment(QString::fromLocal8Bit("区域1").toStdString()); //用来作为显示的名称

Group group_Section1(file.createGroup("Section1"));
group_Section1.setComment(QString::fromLocal8Bit("区域2").toStdString());
//创建第二层group run
Group group_Run0(group_Section0.createGroup("Run0"));
group_Run0.setComment(QString::fromLocal8Bit("测试1").toStdString());
Group group_Run1(group_Section0.createGroup("Run1"));
group_Run1.setComment(QString::fromLocal8Bit("测试2").toStdString());
Group group_Run2(group_Section1.createGroup("Run0"));
group_Run2.setComment(QString::fromLocal8Bit("测试3").toStdString());
Group group_Run3(group_Section1.createGroup("Run1"));
group_Run3.setComment(QString::fromLocal8Bit("测试4").toStdString());
//创建第三层group timedata和spectrum
Group group_Time0(group_Run0.createGroup("TimeData"));
group_Time0.setComment(QString::fromLocal8Bit("实时数据").toStdString());
Group group_Spec0(group_Run0.createGroup("Spectrum"));
group_Spec0.setComment(QString::fromLocal8Bit("处理数据").toStdString());
Group group_Time1(group_Run1.createGroup("TimeData"));
group_Time1.setComment(QString::fromLocal8Bit("实时数据").toStdString());
Group group_Spec1(group_Run1.createGroup("Spectrum"));
group_Spec1.setComment(QString::fromLocal8Bit("处理数据").toStdString());
Group group_Time2(group_Run2.createGroup("TimeData"));
group_Time2.setComment(QString::fromLocal8Bit("实时数据").toStdString());
Group group_Spec2(group_Run2.createGroup("Spectrum"));
group_Spec2.setComment(QString::fromLocal8Bit("处理数据").toStdString());
Group group_Time3(group_Run3.createGroup("TimeData"));
group_Time3.setComment(QString::fromLocal8Bit("实时数据").toStdString());
Group group_Spec3(group_Run3.createGroup("Spectrum"));
group_Spec3.setComment(QString::fromLocal8Bit("处理数据").toStdString());

//创建数据空间
hsize_t dims[RANK]; // dataset dimensions
dims[0] = M;
dims[1] = N;
DataSpace *dataspace = new DataSpace (RANK, dims);

//创建数据集,通道数据
DataSet *dataset_Time = new DataSet (group_Time0.createDataSet("Channel0", PredType::NATIVE_CHAR, *dataspace));
dataset_Time->setComment(QString::fromLocal8Bit("通道1").toStdString());
dataset_Time->write(data, PredType::NATIVE_CHAR);

// 创建数据集属性空间.
int attr_data[2] = { 100, 200};
hsize_t attr_dims[1] = { 2 };
DataSpace attr_dataspace = DataSpace (1,attr_dims );

// 创建数据集的属性.
Attribute attribute = group_Time0.createAttribute( "ATTR_NAME", PredType::STD_I32BE, attr_dataspace);
// 写属性.
attribute.write( PredType::NATIVE_INT, attr_data);

// 关闭数据空间、数据集、group对象.
delete dataspace;
delete dataset_Time;
delete dataset_Spec;
file.close();
}

// catch failure caused by the H5File operations
catch(FileIException error)
{
cout<<error.getDetailMsg();

}
// catch failure caused by the DataSpace operations
catch(DataSpaceIException error)
{
cout<<error.getDetailMsg();

}
// catch failure caused by the Group operations
catch(GroupIException error)
{
cout<<error.getDetailMsg();

}
// catch failure caused by the DataSet operations
catch(DataSetIException error)
{
cout<<error.getDetailMsg();

}

结果如下:

这是安装包里面的bin目录的h5dump程序,可以查看h5文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#define H5F_ACC_RDWR    (H5CHECK H5OPEN 0x0001u)    /*open for read and write    */
#define H5F_ACC_TRUNC (H5CHECK H5OPEN 0x0002u) /*overwrite existing files */
#define H5F_ACC_EXCL (H5CHECK H5OPEN 0x0004u) /*fail if file already exists*/
/* NOTE: 0x0008u was H5F_ACC_DEBUG, now deprecated */
#define H5F_ACC_CREAT (H5CHECK H5OPEN 0x0010u) /*create non-existing files */
#define H5F_ACC_SWMR_WRITE (H5CHECK 0x0020u) /*indicate that this file is
* open for writing in a
* single-writer/multi-reader (SWMR)
* scenario. Note that the
* process(es) opening the file
* for reading must open the file
* with RDONLY access, and use
* the special "SWMR_READ" access
* flag. */
#define H5F_ACC_SWMR_READ (H5CHECK 0x0040u) /*indicate that this file is
* open for reading in a
* single-writer/multi-reader (SWMR)
* scenario. Note that the
* process(es) opening the file
* for SWMR reading must also
* open the file with the RDONLY
* flag. */

/* Value passed to H5Pset_elink_acc_flags to cause flags to be taken from the
* parent file. */
#define H5F_ACC_DEFAULT (H5CHECK H5OPEN 0xffffu) /*ignore setting on lapl */

创建文件之后,文件默认会生成一个根目录,类型是Group,路径是“/”。文件类可以创建Group,DataSet,Attribute,还可以设置comment用来存储一些简单信息。Group,DataSet也可以继续创建上述三种对象,而Attitude只能创建DataSet和Group。file.createGroup(“Section0”)这是使用的相对路径,相当于在根目录下创建Section0目录,所以其绝对路径为/Section0,后续访问的时候也可以通过父目录使用相对目录访问它,也可以在任意目录使用绝对路径访问它,如果在非父目录使用相对路径访问,会引发异常。访问方法是openGroup,其他类型的对象创建和访问的规则也是如此。DataSpace是在创建DataSet和Attribute的时候事先创建的一个数据空间,用来告诉HDF5在存储数据时分配多大的内存空间。它有两个参数,第一个RANK代表维度,第二个参数代表每一个维度的长度。上述例子中就是创建一个二维的三行四列的空间。在创建DataSet和Attribute时还需要指定存储的数据类型,在写入数据(write)的时候也要指定一致的数据类型。数据类型的定义在头文件中有定义,请自行查看。file.close()就是关闭文件句柄,将缓存写入到硬盘中。如果此处不关闭文件句柄,可以调用flush将缓存写入到硬盘。

读取DataSet数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
if(!m_h5FileHandle.exist(objname))  //判断是否存在
return;
DataSet channelDataSet=m_h5FileHandle.openDataSet(objName);//打开数据集
DataType datatype= channelDataSet.getDataType();//获取数据类型

DataSpace dataSpace= channelDataSet.getSpace();//获取数据空间

int rank=dataSpace.getSimpleExtentNdims();//获取维度

hsize_t *dims;

dataSpace.getSimpleExtentDims(dims);//获取每个维度对应的长度

cout<<*dims<<" "<<*(dims+1)<<endl;//假设此处rank=2,那么*dim就是行数,*(dim+1)就是列数

hssize_t sum= dataSpace.getSimpleExtentNpoints();//获取数据总大小 sum=(*dim)x *(dim+1)

if(datatype==PredType::NATIVE_CHAR){

QByteArray dataBuffer;

dataBuffer.resize(sum); //创建对应大小的缓存空间

memset(dataBuffer.data(),0,sum);

channelDataSet.read(dataBuffer.data(),datatype,dataSpace);

}
channelDataSet.close();//关闭数据集

此处是通过绝对路径来访问,访问之前使用exist来判断一下是否存在这个对象,如果访问不存在的对象,也会抛出异常。读取数据集的数据,首先需要获取数据的维度,以及每个维度对应的长度,然后动态创建数组,此处我为了简单,直接将数据全部存入到一个一维字符串数组里面(QByteArray),如果你获取了维度和维度对应的长度也可以根据这个来分割这个一维字符串数组。此处需要注意的是不管打开了Group还是其他对象,使用完了之后一定要close关闭,虽然局部变量释放的时候会自动关闭。

读写DataSet的子集

有时候可能需要向数据集的局部写入数据,或者读取数据集的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// Specify size and shape of subset to write.

hsize_t offset[2], count[2], stride[2], block[2];
hsize_t dimsm[2];

int RANK=2; //子集维度
int DIM0_SUB=3; //子集维度1长度
int DIM1_SUB=4; //子集维度2长度
offset[0] = 1; //子集维度1偏移量
offset[1] = 2; //维度2偏移量

count[0] = DIM0_SUB;//维度1长度
count[1] = DIM1_SUB;//维度2长度
//维度间隙,默认为1数据之间无间隙,=2的时候,写入的子集数据之间间隔为1,以此类推。当子集的数据下标超过总的维度长度,此设置就会无效

stride[0] = 2;
stride[1] = 2;


block[0] = 1; //维度1块大小,暂时未发现这个设置的作用

block[1] = 1;

dimsm[0] = DIM0_SUB;
dimsm[1] = DIM1_SUB;
//写入子集的数据

char *wdata = new char[DIM0_SUB*DIM1_SUB];
char *tmp=NULL;
for(int i =0; i<DIM0_SUB; ++i){
for(int j =0; j<DIM1_SUB; ++j){
tmp = wdata + i*DIM1_SUB +j;
*tmp = 100;
}
}
char rdata[3][4];
H5File file(filename,H5F_ACC_RDWR);
//根据绝对路径打开数据集

DataSet dataset =file.openDataSet("/Section0/Run0/TimeData/Channel0");

//子集数据空间
DataSpace memSpace(RANK, dimsm, NULL);
//原数据集空间
DataSpace dataSpace= dataset.getSpace();
//H5S_SELECT_SET是用子集覆盖原来相同坐标的数据,还有其他选项,OR XOR AND 等选项,可以进入源码自行阅读

//设置超实验室
dataSpace.selectHyperslab(H5S_SELECT_SET, count, offset, stride, block);
//写入子集

dataset.write(wdata,PredType::NATIVE_CHAR,memSpace,dataSpace);
//读取子集

dataset.read(rdata,PredType::NATIVE_CHAR,memSpace,dataSpace);
file.close();
for(int i=0;i<DIM0_SUB;i++){
cout<<endl;
for(int j=0;j<DIM1_SUB;j++)

cout<<" "<<(rdata[i][j]);

}

不设置间隙stride[[0]]=1;stride[[1]]=1;结果如下:

设置间隙stride[[0]]=2;stride[[1]]=2;结果如下:

读取出来的子集结果:

char字符’d‘转化为int就是100

写入数据时压缩处理

1
2
3
4
5
6
7
8
9
10
11
//创建压缩空间
hsize_t chunk_dims[2]={5,10};
DSetCreatPropList *plist = new DSetCreatPropList;
plist->setChunk(2, chunk_dims);
//设置Deflate=6使用zlib压缩,注释掉则使用下列的szip
plist->setDeflate(6);
// unsigned szip_options_mask = H5_SZIP_NN_OPTION_MASK;
// unsigned szip_pixels_per_block = 16;
// plist->setSzip(szip_options_mask, szip_pixels_per_block);
//创建数据集,通道数据
DataSet *dataset_Time = new DataSet (group_Time0.createDataSet("Channel0", PredType::NATIVE_CHAR, *dataspace,*plist));

这个代码是替换创建HDF5文件里面创建数据集的过程,这个代码添加了压缩属性设置,支持zlib个szip两种压缩模式。读取数据集的时候和没有压缩一样即可。但是在使用过程中,发现压缩前和压缩后的文件大小并没有区别,这个问题后续再来研究。


HDF5_CPP使用教程
http://yoursite.com/2019/05/21/HDF5-CPP使用教程/
作者
还在输入
发布于
2019年5月21日
许可协议