Writing/reading 大向量数据到 C++ 中的二进制文件
Writing/reading large vectors of data to binary file in c++
我有一个 c++ 程序,它通过将网格化的人口数据从 ascii 文件读取到一个大的 8640x3432 元素双精度向量来计算给定半径内的人口。将 ascii 数据读入向量需要大约 30 秒(遍历每一列和每一行),而程序的其余部分只需要几秒钟。我被要求通过将人口数据写入二进制文件来加快此过程,据说这会更快地读取。
ascii 数据文件有几行 header 行,这些行给出了一些数据规范,例如列数和行数,然后是每个网格单元格的人口数据,其格式为 3432 行,每行 8640 个数字,以空格隔开。人口数据数字是混合格式,可以是 0、十进制值 (0.000685648) 或科学记数法的值 (2.687768e-05)。
我发现了一些 reading/writing 包含二进制向量的结构示例,并尝试实现类似的东西,但 运行 遇到了问题。当我在同一个程序中写入和读取向量 to/from 二进制文件时,它似乎可以工作并为我提供所有正确的值,但随后它以 "segment fault: 11" 或内存分配错误结束那一个"pointer being freed was not allocated"。而且,如果我尝试只从先前写入的二进制文件中读取数据(在同一程序 运行 中没有 re-writing),那么它会给我 header 变量就好了,但给出了在给我矢量数据之前给我一个段错误。
任何关于我可能做错了什么的建议,或者更好的方法,将不胜感激!我在 mac 上编译和 运行ning,目前我没有 boost 或其他 non-standard 库。 (注意:我在编码方面非常陌生,必须通过深入学习来学习,所以我可能会遗漏很多基本概念和术语——抱歉!)。
这是我想出的代码:
# 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
下面是我尝试从 pre-existing 二进制文件中读取时得到的输出:
File Header Values:
Columns (binary): 8640
Rows (binary):3432
Spot Check Vector Values:
Segmentation fault: 11
在此先感谢您的帮助!
当您将 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 的协议缓冲区,但它们只能解决上面列出的部分问题。
我有一个 c++ 程序,它通过将网格化的人口数据从 ascii 文件读取到一个大的 8640x3432 元素双精度向量来计算给定半径内的人口。将 ascii 数据读入向量需要大约 30 秒(遍历每一列和每一行),而程序的其余部分只需要几秒钟。我被要求通过将人口数据写入二进制文件来加快此过程,据说这会更快地读取。
ascii 数据文件有几行 header 行,这些行给出了一些数据规范,例如列数和行数,然后是每个网格单元格的人口数据,其格式为 3432 行,每行 8640 个数字,以空格隔开。人口数据数字是混合格式,可以是 0、十进制值 (0.000685648) 或科学记数法的值 (2.687768e-05)。
我发现了一些 reading/writing 包含二进制向量的结构示例,并尝试实现类似的东西,但 运行 遇到了问题。当我在同一个程序中写入和读取向量 to/from 二进制文件时,它似乎可以工作并为我提供所有正确的值,但随后它以 "segment fault: 11" 或内存分配错误结束那一个"pointer being freed was not allocated"。而且,如果我尝试只从先前写入的二进制文件中读取数据(在同一程序 运行 中没有 re-writing),那么它会给我 header 变量就好了,但给出了在给我矢量数据之前给我一个段错误。
任何关于我可能做错了什么的建议,或者更好的方法,将不胜感激!我在 mac 上编译和 运行ning,目前我没有 boost 或其他 non-standard 库。 (注意:我在编码方面非常陌生,必须通过深入学习来学习,所以我可能会遗漏很多基本概念和术语——抱歉!)。
这是我想出的代码:
# 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
下面是我尝试从 pre-existing 二进制文件中读取时得到的输出:
File Header Values:
Columns (binary): 8640
Rows (binary):3432
Spot Check Vector Values:
Segmentation fault: 11
在此先感谢您的帮助!
当您将 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 的协议缓冲区,但它们只能解决上面列出的部分问题。