使用 boost iostreams 读取和写入数组到压缩文件

Read and write array to compressed file with boost iostreams

我想将一个数组写入一个文件,边压缩边压缩它。

稍后,我想从该文件中读取数组,边解压边解压。

Boost 的 Iostream 似乎是一个不错的选择,所以我构建了以下代码。不幸的是,输出和输入数据最后比较不相等。但他们几乎做到了:

Output         Input
0.8401877284   0.8401880264
0.3943829238   0.3943830132
0.7830992341   0.7830989957
0.7984400392   0.7984399796
0.9116473794   0.9116470218
0.1975513697   0.1975509971
0.3352227509   0.3352229893

这表明每个浮点数的最低有效字节正在发生变化,或者发生了什么。但是,压缩应该是无损的,因此这不是预期或期望的。给出了什么?

//Compile with: g++ test.cpp --std=c++11 -lz -lboost_iostreams
#include <fstream>
#include <iostream>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <cstdlib>
#include <vector>
#include <iomanip>

int main() 
{
    using namespace std;
    using namespace boost::iostreams;

    const int NUM = 10000;

    std::vector<float> data_out;
    std::vector<float> data_in;
    data_in.resize(NUM);
    for(float i=0;i<NUM;i++)
      data_out.push_back(rand()/(float)RAND_MAX);

    {
      ofstream file("/z/hello.z", ios_base::out | ios_base::binary);
      filtering_ostream out;
      out.push(zlib_compressor());
      out.push(file);

      for(const auto d: data_out)
        out<<d;
    }

    {
      ifstream file_in("hello.z", ios_base::in | ios_base::binary);
      filtering_istream in;
      in.push(zlib_decompressor());
      in.push(file_in);

      for(float i=0;i<NUM;i++)
        in>>data_in[i];
    }

    bool all_good=true;
    for(int i=0;i<NUM;i++){
      cout<<std::setprecision(10)<<data_out[i]<<"   "<<data_in[i]<<endl;
      all_good &= (data_out[i]==data_in[i]);
    }

    cout<<"Good? "<<(int)all_good<<endl;
}

而且,是的,我非常喜欢按照我的方式使用流运算符,而不是一次推或拉整个向量块。

问题不在于压缩,而在于序列化向量值的方式。

如果您禁用压缩并将大小限制为 10 个元素以便于检查,您可以看到生成的文件如下所示:

0.001251260.5635850.1933040.808740.5850090.4798730.3502910.8959620.822840.746605

如您所见,数字以文本形式表示,小数位数有限,并且没有分隔符。纯属偶然(因为您只处理小于 1.0 的值),您的程序能够产生遥感结果。

发生这种情况是因为您使用 stream operator << 将数字类型格式化为文本。


最简单的解决方案似乎是使用 boost::serialization 来处理读写(并使用 boost::iostreams 作为底层压缩流)。我使用了二进制存档,但你也可以使用文本存档(只需将 binary_ 替换为 text_)。

示例代码:

#include <fstream>
#include <iostream>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/zlib.hpp>

#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/vector.hpp>

#include <cstdlib>
#include <vector>
#include <iomanip>

int main() 
{
    using namespace std;
    using namespace boost::iostreams;

    const int NUM = 10;

    std::vector<float> data_out;
    for (float i = 0; i < NUM; i++) {
        data_out.push_back(rand() / (float)RAND_MAX);
    }

    {
        ofstream file("hello.z", ios_base::out | ios_base::binary);
        filtering_ostream out;
        out.push(zlib_compressor());
        out.push(file);

        boost::archive::binary_oarchive oa(out);
        oa & data_out;
    }

    std::vector<float> data_in;
    {
        ifstream file_in("hello.z", ios_base::in | ios_base::binary);
        filtering_istream in;
        in.push(zlib_decompressor());
        in.push(file_in);

        boost::archive::binary_iarchive ia(in);
        ia & data_in;
    }

    bool all_good=true;
    for(int i=0;i<NUM;i++){
      cout<<std::setprecision(10)<<data_out[i]<<"   "<<data_in[i]<<endl;
      all_good &= (data_out[i]==data_in[i]);
    }

    cout<<"Good? "<<(int)all_good<<endl;
}

控制台输出:

0.001251258887   0.001251258887
0.563585341   0.563585341
0.1933042407   0.1933042407
0.8087404966   0.8087404966
0.5850093365   0.5850093365
0.4798730314   0.4798730314
0.3502914608   0.3502914608
0.8959624171   0.8959624171
0.822840035   0.822840035
0.7466048002   0.7466048002
Good? 1

一个小问题是你没有序列化vector的大小,所以读的时候要一直读到流的末尾。

正如 Dan Mašek 在 中指出的那样,我使用的 << 流运算符在压缩之前将我的浮点数据转换为文本表示。出于某种原因,我没想到会这样。

使用序列化库是避免这种情况的一种方法,但除了可能 overhead.

之外还会引入额外的依赖项

因此,我在浮点数据上使用了reinterpret_castostream::write()的方法来写入数据,一次不转换一个字符。阅读使用类似的方法。可以通过增加一次写入的字符数来提高效率。

#include <fstream>
#include <iostream>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <cstdlib>
#include <vector>
#include <iomanip>

int main() 
{
    using namespace std;
    using namespace boost::iostreams;

    const int NUM = 10000;

    std::vector<float> data_out;
    std::vector<float> data_in;
    data_in.resize(NUM);
    for(float i=0;i<NUM;i++)
      data_out.push_back(233*(rand()/(float)RAND_MAX));

    {
      ofstream file("/z/hello.z", ios_base::out | ios_base::binary);
      filtering_ostream out;
      out.push(zlib_compressor());
      out.push(file);

      char *dptr = reinterpret_cast<char*>(data_out.data());

      for(int i=0;i<sizeof(float)*NUM;i++)
        out.write(&dptr[i],1);
    }

    {
      ifstream file_in("hello.z", ios_base::in | ios_base::binary);
      filtering_istream in;
      in.push(zlib_decompressor());
      in.push(file_in);

      char *dptr = reinterpret_cast<char*>(data_in.data());

      for(int i=0;i<sizeof(float)*NUM;i++)
        in.read(&dptr[i],1);
    }

    bool all_good=true;
    for(int i=0;i<NUM;i++){
      cout<<std::setprecision(10)<<data_out[i]<<"   "<<data_in[i]<<endl;
      all_good &= (data_out[i]==data_in[i]);
    }

    cout<<"Good? "<<(int)all_good<<endl;
}