用c ++编写/读取大数据向量到二进制文件

时间:2015-03-05 20:16:10

标签: c++ vector binaryfiles

我有一个c ++程序,通过将ascii文件中的网格化人口数据读入一个大的8640x3432元素的双精度矢量来计算给定半径内的人口。将ascii数据读入向量需要约30秒(在每列和每行上循环),而程序的其余部分只需要几秒钟。我被要求通过将填充数据写入二进制文件来加速这个过程,这个文件应该更快地读取。

ascii数据文件有一些标题行,它们提供一些数据规格,如列数和行数,然后是每个网格单元格的填充数据,格式为3432行8640个数字,用空格分隔。人口数据编号是混合格式,可以是0,十进制值(0.000685648),也可以是科学记数法(2.687768e-05)。

我找到了一些读取/写入包含二进制向量的结构的例子,并试图实现类似的东西,但遇到了问题。当我在同一程序中向/从二进制文件中写入和读取向量时,它似乎工作并给我所有正确的值,但它以“段错误:11”或内存分配错误结束“没有分配被释放的指针”。如果我尝试从先前编写的二进制文件中读取数据(不在相同的程序运行中重写它),那么它给我标题变量就好了,但在给我矢量数据之前给了我一个段错误。

任何关于我可能做错了什么的建议,或者以更好的方式做到这一点都将非常感谢!我正在编译并在mac上运行,目前我没有boost或其他非标准库。 (注意:我在编码方面非常新,我不得不通过深入学习来学习,所以我可能会遗漏很多基本概念和术语 - 抱歉!)。

以下是我提出的代码:

# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <fstream>
# include <iostream>
# include <vector>
# include <string.h>

using namespace std;

//Define struct for population file data and initialize one struct variable for reading in ascii (A) and one for reading in binary (B)
struct popFileData
{
    int nRows, nCol;
    vector< vector<double> > popCount; //this will end up having 3432x8640 elements
} popDataA, popDataB;

int main() {

    string gridFname = "sample";

    double dum;
    vector<double> tempVector;

    //open ascii population grid file to stream
    ifstream gridFile;
    gridFile.open(gridFname + ".asc");

    int i = 0, j = 0;

    if (gridFile.is_open())
    {
        //read in header data from file
        string fileLine;
        gridFile >> fileLine >> popDataA.nCol;
        gridFile >> fileLine >> popDataA.nRows;

        popDataA.popCount.clear();

        //read in vector data, point-by-point
        for (i = 0; i < popDataA.nRows; i++)
        {
            tempVector.clear();

            for (j = 0; j<popDataA.nCol; j++)
            {
                gridFile >> dum;
                tempVector.push_back(dum);
            }
            popDataA.popCount.push_back(tempVector);
        }
        //close ascii grid file
        gridFile.close();
    }
    else
    {
        cout << "Population file read failed!" << endl;
    }

    //create/open binary file
    ofstream ofs(gridFname + ".bin", ios::trunc | ios::binary);
    if (ofs.is_open())
    {
        //write struct to binary file then close binary file
        ofs.write((char *)&popDataA, sizeof(popDataA));
        ofs.close();
    }
    else cout << "error writing to binary file" << endl;

    //read data from binary file into popDataB struct
    ifstream ifs(gridFname + ".bin", ios::binary);
    if (ifs.is_open())
    {
        ifs.read((char *)&popDataB, sizeof(popDataB));
        ifs.close();
    }
    else cout << "error reading from binary file" << endl;

    //compare results of reading in from the ascii file and reading in from the binary file
    cout << "File Header Values:\n";
    cout << "Columns (ascii vs binary): " << popDataA.nCol << " vs. " << popDataB.nCol << endl;
    cout << "Rows (ascii vs binary):" << popDataA.nRows << " vs." << popDataB.nRows << endl;

    cout << "Spot Check Vector Values: " << endl;
    cout << "Index 0,0: " << popDataA.popCount[0][0] << " vs. " << popDataB.popCount[0][0] << endl;
    cout << "Index 3431,8639: " << popDataA.popCount[3431][8639] << " vs. " << popDataB.popCount[3431][8639] << endl;
    cout << "Index 1600,4320: " << popDataA.popCount[1600][4320] << " vs. " << popDataB.popCount[1600][4320] << endl;

    return 0;
}

以下是我在同一次运行中写入和读取二进制文件时的输出:

File Header Values:
Columns (ascii vs binary): 8640 vs. 8640
Rows (ascii vs binary):3432 vs.3432
Spot Check Vector Values: 
Index 0,0: 0 vs. 0
Index 3431,8639: 0 vs. 0
Index 1600,4320: 25.2184 vs. 25.2184
a.out(11402,0x7fff77c25310) malloc: *** error for object 0x7fde9821c000: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6

如果我只是尝试从预先存在的二进制文件中读取,那么这是我得到的输出:

File Header Values:
Columns (binary): 8640
Rows (binary):3432
Spot Check Vector Values: 
Segmentation fault: 11

提前感谢您的帮助!

1 个答案:

答案 0 :(得分:4)

当您将popDataA写入文件时,您正在编写向量向量的二进制表示。然而,这确实是一个非常小的对象,包括指向实际数据的指针(在本例中是一系列向量)和一些大小信息。

当它回读popDataB时,它有点有效!但只是因为popDataA中的原始指针现在位于popDataB中,并且它指向内存中的相同内容。最后事情变得疯狂,因为当释放向量的内存时,代码会尝试释放popDataA引用的数据两次(一次用于popDataA,再次用于popDataB 。)

简短的版本是,以这种方式将矢量写入文件是不合理的。

那该怎么办?最好的方法是首先确定您的数据表示。它将像ASCII格式一样,指定在哪里写入的值,并将包含有关矩阵大小的信息,以便您知道在读取它们时需要分配的矢量大小。

在半伪代码中,写作看起来像:

int nrow=...;
int ncol=...;
ofs.write((char *)&nrow,sizeof(nrow));
ofs.write((char *)&ncol,sizeof(ncol));
for (int i=0;i<nrow;++i) {
    for (int j=0;j<ncol;++j) {
        double val=data[i][j];
        ofs.write((char *)&val,sizeof(val));
    }
}

阅读将是相反的:

ifs.read((char *)&nrow,sizeof(nrow));
ifs.read((char *)&ncol,sizeof(ncol));
// allocate data-structure of size nrow x ncol
// ...
for (int i=0;i<nrow;++i) {
    for (int j=0;j<ncol;++j) {
        double val;
        ifs.read((char *)&val,sizeof(val));
        data[i][j]=val;
    }
}

尽管如此,您应该考虑不要将内容写入这样的二进制文件中。这些特殊的二进制格式倾向于存在,远远超过其预期的效用,并且往往会受到以下影响:

  • 缺乏文件
  • 缺乏可扩展性
  • 没有版本控制信息的格式更改
  • 在不同计算机上使用保存数据时出现的问题,包括字节序问题,整数的不同默认大小等。

相反,我强烈建议您使用第三方库。对于科学数据,HDF5和netcdf4是解决上述所有问题的好选择,并提供可以在不了解您的特定程序的情况下检查数据的工具。

轻量级选项包括Boost序列化库和Google的协议缓冲区,但这些只能解决上面列出的一些问题。