如何最好地将 std::vector < std::string > 容器写入 HDF5 数据集?

Posted

技术标签:

【中文标题】如何最好地将 std::vector < std::string > 容器写入 HDF5 数据集?【英文标题】:How to best write out a std::vector < std::string > container to a HDF5 dataset? 【发布时间】:2010-10-09 12:49:34 【问题描述】:

给定一个字符串向量,将它们写入 HDF5 数据集的最佳方法是什么?目前我正在做类似以下的事情:

  const unsigned int MaxStrLength = 512;

  struct TempContainer 
    char string[MaxStrLength];
  ;

  void writeVector (hid_t group, std::vector<std::string> const & v)
  
    //
    // Firstly copy the contents of the vector into a temporary container
    std::vector<TempContainer> tc;
    for (std::vector<std::string>::const_iterator i = v.begin ()
                                              , end = v.end ()
      ; i != end
      ; ++i)
    
      TempContainer t;
      strncpy (t.string, i->c_str (), MaxStrLength);
      tc.push_back (t);
    


    //
    // Write the temporary container to a dataset
    hsize_t     dims[] =  tc.size ()  ;
    hid_t dataspace = H5Screate_simple(sizeof(dims)/sizeof(*dims)
                               , dims
                               , NULL);

    hid_t strtype = H5Tcopy (H5T_C_S1);
    H5Tset_size (strtype, MaxStrLength);

    hid_t datatype = H5Tcreate (H5T_COMPOUND, sizeof (TempConainer));
    H5Tinsert (datatype
      , "string"
      , HOFFSET(TempContainer, string)
      , strtype);

    hid_t dataset = H5Dcreate1 (group
                          , "files"
                          , datatype
                          , dataspace
                          , H5P_DEFAULT);

    H5Dwrite (dataset, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &tc[0] );

    H5Dclose (dataset);
    H5Sclose (dataspace);
    H5Tclose (strtype);
    H5Tclose (datatype);

至少,我真的很想更改上述内容,以便:

    它使用可变长度字符串 我不需要临时容器

我对如何存储数据没有任何限制,例如,如果有更好的方法,它不必是 COMPOUND 数据类型。

编辑:只是为了缩小问题的范围,我比较熟悉在 C++ 端处理数据,我最需要帮助的是 HDF5 端。

感谢您的帮助。

【问题讨论】:

【参考方案1】:

如果您正在查看更简洁的代码:我建议您创建一个函子,它将接受一个字符串并将其保存到 HDF5 容器中(以所需的模式)。理查德,我使用了错误的算法,请重新检查!

std::for_each(v.begin(), v.end(), write_hdf5);

struct hdf5 : public std::unary_function<std::string, void> 
    hdf5() : _dataset(...)  // initialize the HDF5 db
    ~hdf5() : _dataset(...)  // close the the HDF5 db
    void operator(std::string& s) 
            // append 
            // use s.c_str() ?
    
;

这有助于开始吗?

【讨论】:

嗯 - 是的,我希望能够达到这种风格 - 但是,我不确定它是否 (a) 可能和 (b) 有效。感谢您的回答。 我对 HDF5 非常陌生,所以我不知道在你有“// append”的地方需要写什么。 我只听说过 HDF5。我的意思是在评论下附加你正在做的任何事情 // 将临时容器写入数据集。 这是问题的症结所在。 “H5Dwrite”方法接受一个“void*”参数并写入,它有点像“memcpy”或“memmove”,你给它一个大小和一个数据块。至少我现在是这么认为的! :) 所以使用 your_data_string.c_str() 和 your_data_string.size()。 void* 实际上是一种让任何类型的数据通过的方式。我想知道为什么你需要结构 TempContainer。【参考方案2】:

我不知道HDF5,但你可以使用

struct TempContainer 
    char* string;
;

然后以这种方式复制字符串:

TempContainer t;
t.string = strdup(i->c_str());
tc.push_back (t);

这将分配一个精确大小的字符串,并且在从容器中插入或读取时也会有很大的改进(在您的示例中,复制了一个数组,在这种情况下只有一个指针)。你也可以使用 std::vector:

std::vector<char *> tc;
...
tc.push_back(strdup(i->c_str());

【讨论】:

当然。理想情况下,我根本不需要临时容器。这段代码增加了内存需要显式释放的轻微缺点。【参考方案3】:

您可以使用简单的 std::vector 代替 TempContainer(您也可以将其模板化以匹配 T -> basic_string 。 像这样的:

#include <algorithm>
#include <vector>
#include <string>
#include <functional>

class StringToVector
  : std::unary_function<std::vector<char>, std::string> 
public:
  std::vector<char> operator()(const std::string &s) const 
    // assumes you want a NUL-terminated string
    const char* str = s.c_str();
    std::size_t size = 1 + std::strlen(str);
    // s.size() != strlen(s.c_str())
    std::vector<char> buf(&str[0], &str[size]);
    return buf;
  
;

void conv(const std::vector<std::string> &vi,
          std::vector<std::vector<char> > &vo)

  // assert vo.size() == vi.size()
  std::transform(vi.begin(), vi.end(),
                 vo.begin(),
                 StringToVector());

【讨论】:

【参考方案4】:

[非常感谢dirkgently 帮助回答这个问题。]

要在 HDF5 中写入可变长度字符串,请使用以下命令:

// Create the datatype as follows
hid_t datatype = H5Tcopy (H5T_C_S1);
H5Tset_size (datatype, H5T_VARIABLE);

// 
// Pass the string to be written to H5Dwrite
// using the address of the pointer!
const char * s = v.c_str ();
H5Dwrite (dataset
  , datatype
  , H5S_ALL
  , H5S_ALL
  , H5P_DEFAULT
  , &s );

编写容器的一种解决方案是单独编写每个元素。这可以使用hyperslabs 来实现。

例如:

class WriteString

public:
  WriteString (hid_t dataset, hid_t datatype
      , hid_t dataspace, hid_t memspace)
    : m_dataset (dataset), m_datatype (datatype)
    , m_dataspace (dataspace), m_memspace (memspace)
    , m_pos () 

private:
  hid_t m_dataset;
  hid_t m_datatype;
  hid_t m_dataspace;
  hid_t m_memspace;
  int m_pos;

//...

public:
  void operator ()(std::vector<std::string>::value_type const & v)
  
    // Select the file position, 1 record at position 'pos'
    hsize_t count[] =  1  ;
    hsize_t offset[] =  m_pos++  ;
    H5Sselect_hyperslab( m_dataspace
      , H5S_SELECT_SET
      , offset
      , NULL
      , count
      , NULL );

    const char * s = v.c_str ();
    H5Dwrite (m_dataset
      , m_datatype
      , m_memspace
      , m_dataspace
      , H5P_DEFAULT
      , &s );
        
;

// ...

void writeVector (hid_t group, std::vector<std::string> const & v)

  hsize_t     dims[] =  m_files.size ()   ;
  hid_t dataspace = H5Screate_simple(sizeof(dims)/sizeof(*dims)
                                    , dims, NULL);

  dims[0] = 1;
  hid_t memspace = H5Screate_simple(sizeof(dims)/sizeof(*dims)
                                    , dims, NULL);

  hid_t datatype = H5Tcopy (H5T_C_S1);
  H5Tset_size (datatype, H5T_VARIABLE);

  hid_t dataset = H5Dcreate1 (group, "files", datatype
                             , dataspace, H5P_DEFAULT);

  // 
  // Select the "memory" to be written out - just 1 record.
  hsize_t offset[] =  0  ;
  hsize_t count[] =  1  ;
  H5Sselect_hyperslab( memspace, H5S_SELECT_SET, offset
                     , NULL, count, NULL );

  std::for_each (v.begin ()
      , v.end ()
      , WriteStrings (dataset, datatype, dataspace, memspace));

  H5Dclose (dataset);
  H5Sclose (dataspace);
  H5Sclose (memspace);
  H5Tclose (datatype);
      

【讨论】:

你知道吗? HDF5 是我一直想读写的东西之一。但拖延是我的中间名,但没有实现。多亏了你,我决定这次给它一个更专注的镜头。如果可能的话,我非常非常想知道你在哪里使用它。 我们正在寻求改变我们的静态分析工具存储从分析中收集的数据的方式。数据将包含树状结构(范围、类型等)和诊断列表。在这个阶段,我只是评估 HDF5 处理不同类型数据的能力。 这个问题(我问过的)概述了我们正在评估的功能类型:***.com/questions/547195/…【参考方案5】:

这是一些使用 HDF5 c++ API 编写可变长度字符串向量的工作代码。

我在其他帖子中加入了一些建议:

    使用 H5T_C_S1 和 H5T_VARIABLE 使用string::c_str()获取指向字符串的指针 将指针放入 vectorchar* 并传递给 HDF5 API

没有必要创建昂贵的字符串副本(例如使用strdup())。 c_str() 返回一个指向底层字符串的空终止数据的指针。这正是该功能的用途。当然,带有嵌入空值的字符串不适用于此...

std::vector 保证有连续的底层存储,因此使用vectorvector::data() 与使用原始数组相同,但当然比笨重、老式的 c 处理方式更整洁、更安全.

#include "H5Cpp.h"
void write_hdf5(H5::H5File file, const std::string& data_set_name,
                const std::vector<std::string>& strings )

    H5::Exception::dontPrint();

    try
    
        // HDF5 only understands vector of char* :-(
        std::vector<const char*> arr_c_str;
        for (unsigned ii = 0; ii < strings.size(); ++ii) 
            arr_c_str.push_back(strings[ii].c_str());

        //
        //  one dimension
        // 
        hsize_t     str_dimsf[1] arr_c_str.size();
        H5::DataSpace   dataspace(1, str_dimsf);

        // Variable length string
        H5::StrType datatype(H5::PredType::C_S1, H5T_VARIABLE); 
        H5::DataSet str_dataset = file.createDataSet(data_set_name, datatype, dataspace);

        str_dataset.write(arr_c_str.data(), datatype);
    
    catch (H5::Exception& err)
    
        throw std::runtime_error(string("HDF5 Error in " ) 
                                    + err.getFuncName()
                                    + ": "
                                    + err.getDetailMsg());


    

【讨论】:

不错!但是您如何将文件中的内容读回std::vector&lt;std::string&gt; 这适用于什么操作系统?因为看起来这会导致很多机器上的段错误,或者只是破坏你的数据。我很怀疑,因为我做了类似的事情,在 Linux 上工作但在 OSX 上失败了。【参考方案6】:

为了能够阅读 std::vector&lt;std::string&gt;,我根据 Leo 在此处 https://***.com/a/15220532/364818 的提示发布了我的解决方案。

我混合了 C 和 C++ API。请随时编辑它并使其更简单。

请注意,当您调用 read 时,HDF5 API 会返回 char*pointers 列表。这些char*指针在使用后必须释放,否则会出现内存泄漏。

使用示例

H5::Attribute Foo = file.openAttribute("Foo");
std::vector<std::string> foos
Foo >> foos;

这是代码

  const H5::Attribute& operator>>(const H5::Attribute& attr0, std::vector<std::string>& array)
  
      H5::Exception::dontPrint();

      try
      
          hid_t attr = attr0.getId();

          hid_t atype = H5Aget_type(attr);
          hid_t aspace = H5Aget_space(attr);
          int rank = H5Sget_simple_extent_ndims(aspace);
          if (rank != 1) throw PBException("Attribute " + attr0.getName() + " is not a string array");

          hsize_t sdim[1];
          herr_t ret = H5Sget_simple_extent_dims(aspace, sdim, NULL);
          size_t size = H5Tget_size (atype);
          if (size != sizeof(void*))
          
              throw PBException("Internal inconsistency. Expected pointer size element");
          

          // HDF5 only understands vector of char* :-(
          std::vector<char*> arr_c_str(sdim[0]);

          H5::StrType stringType(H5::PredType::C_S1, H5T_VARIABLE);
          attr0.read(stringType, arr_c_str.data());
          array.resize(sdim[0]);
          for(int i=0;i<sdim[0];i++)
          
              // std::cout << i << "=" << arr_c_str[i] << std::endl;
              array[i] = arr_c_str[i];
              free(arr_c_str[i]);
          

      
      catch (H5::Exception& err)
      
          throw std::runtime_error(string("HDF5 Error in " )
                                    + err.getFuncName()
                                    + ": "
                                    + err.getDetailMsg());


      

      return attr0;
  

【讨论】:

【参考方案7】:

我遇到了类似的问题,但需要注意的是,我希望将字符串向量存储为 属性。属性的棘手之处在于我们不能使用像 hyperslabs 这样的花哨的数据空间功能(至少对于 C++ API)。

但在任何一种情况下,将字符串向量输入到数据集中的单个条目中可能很有用(例如,如果您总是希望将它们一起读取)。在这种情况下,所有的魔法都来自 type,而不是数据空间本身。

基本上有4个步骤:

    创建一个指向字符串的vector&lt;const char*&gt;。 创建一个指向向量并包含其长度的hvl_t 结构。 创建数据类型。这是一个 H5::VarLenType 包装一个(可变长度)H5::StrType。 将hvl_t 类型写入数据集。

此方法真正好的部分是您将整个条目填充到 HDF5 认为的标量值中。这意味着将其作为属性(而不是数据集)是微不足道的。

无论您选择此解决方案还是选择每个字符串都在其自己的数据集条目中的解决方案,这可能也是所需性能的问题:如果您正在寻找对特定字符串的随机访问,最好将字符串写入一个数据集,以便它们可以被索引。如果您总是将它们全部一起阅读,则此解决方案可能也同样有效。

下面是一个简短的例子,说明如何使用 C++ API 和一个简单的标量数据集:

#include <vector>
#include <string>
#include "H5Cpp.h"

int main(int argc, char* argv[]) 
  // Part 0: make up some data
  std::vector<std::string> strings;
  for (int iii = 0; iii < 10; iii++) 
    strings.push_back("this is " + std::to_string(iii));
  

  // Part 1: grab pointers to the chars
  std::vector<const char*> chars;
  for (const auto& str: strings) 
    chars.push_back(str.data());
  

  // Part 2: create the variable length type
  hvl_t hdf_buffer;
  hdf_buffer.p = chars.data();
  hdf_buffer.len = chars.size();

  // Part 3: create the type
  auto s_type = H5::StrType(H5::PredType::C_S1, H5T_VARIABLE);
  s_type.setCset(H5T_CSET_UTF8); // just for fun, you don't need this
  auto svec_type = H5::VarLenType(&s_type);

  // Part 4: write the output to a scalar dataset
  H5::H5File out_file("vtest.h5", H5F_ACC_EXCL);
  H5::DataSet dataset(
    out_file.createDataSet("the_ds", svec_type, H5S_SCALAR));
  dataset.write(&hdf_buffer, svec_type);

  return 0;

【讨论】:

【参考方案8】:

我迟到了,但我已经根据 cmets 关于 segfaults 修改了 Leo Goodstadt 的答案。我在linux上,但我没有这样的问题。我写了 2 个函数,一个用于将 std::string 的向量写入打开的 H5File 中给定名称的数据集,另一个用于将结果数据集读回 std::string 的向量。请注意,类型之间可能有几次不必要的复制,可以更优化。这是用于编写和阅读的工作代码:

void write_varnames( const std::string& dsetname, const std::vector<std::string>& strings, H5::H5File& f)
  
    H5::Exception::dontPrint();

    try
      
        // HDF5 only understands vector of char* :-(
        std::vector<const char*> arr_c_str;
        for (size_t ii = 0; ii < strings.size(); ++ii)
      
        arr_c_str.push_back(strings[ii].c_str());
      

        //
        //  one dimension
        // 
        hsize_t     str_dimsf[1] arr_c_str.size();
        H5::DataSpace   dataspace(1, str_dimsf);

        // Variable length string
        H5::StrType datatype(H5::PredType::C_S1, H5T_VARIABLE); 
        H5::DataSet str_dataset = f.createDataSet(dsetname, datatype, dataspace);

        str_dataset.write(arr_c_str.data(), datatype);
      
    catch (H5::Exception& err)
      
        throw std::runtime_error(std::string("HDF5 Error in ")  
                 + err.getFuncName()
                 + ": "
                 + err.getDetailMsg());


      
  

阅读:

std::vector<std::string> read_string_dset( const std::string& dsname, H5::H5File& f )
  
    H5::DataSet cdataset = f.openDataSet( dsname );


    H5::DataSpace space = cdataset.getSpace();

    int rank = space.getSimpleExtentNdims();

    hsize_t dims_out[1];

    int ndims = space.getSimpleExtentDims( dims_out, NULL);

    size_t length = dims_out[0];

    std::vector<const char*> tmpvect( length, NULL );

    fprintf(stdout, "In read STRING dataset, got number of strings: [%ld]\n", length );

    std::vector<std::string> strs(length);
    H5::StrType datatype(H5::PredType::C_S1, H5T_VARIABLE); 
    cdataset.read( tmpvect.data(), datatype);

    for(size_t x=0; x<tmpvect.size(); ++x)
      
        fprintf(stdout, "GOT STRING [%s]\n", tmpvect[x] );
        strs[x] = tmpvect[x];
      

    return strs;
  

【讨论】:

以上是关于如何最好地将 std::vector < std::string > 容器写入 HDF5 数据集?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 dlib 中的矩阵转换为 std::vector

如何在 C++ 中合法地声明向量变量?

如何将 std::vector<std::vector<double>> 转换为 torch::Tensor?

如何将 std::vector<std::vector<double>> 转换为 Rcpp::Dataframe 或 Rcpp::NumericMatrix

alg-最长回文字符串

如何默认初始化 std::vector