TL; DR
在C ++中使用STL惯用法迭代二进制文件进行读取,转换,然后再次写出数据会有什么好方法?这些文件可能非常大(几百MB),所以我不想一次将整个文件加载到内存中。
更多背景
我正在尝试改进对二进制文件执行各种操作的实用程序。这些文件包含一组记录,包括标题和数据。该实用程序提供了将文件转储为文本,过滤掉某些记录,提取某些记录,追加记录等的选项。不幸的是,所有这些函数都具有从文件中读取和写入的代码,并将其复制并粘贴到每个函数中,因此单个源文件包含大量冗余代码,并开始失控。
我只是刚刚开始使用C ++和STL,但这似乎应该可以使用某种模板/迭代器魔法,但我找不到解释这种情况的好例子。我可能追求的另一个策略是将文件访问包装在提供GetNextRecord和WriteNextRecord方法的类中。
以下是我正在处理的自包含/(极其)简化版本。是否有一种很好的方法来编写函数来读取WriteMyDataFile创建的文件中的数据并创建一个新的输出文件,删除包含“i”字符的所有记录?我希望抽象出文件的读/写,以便该函数主要用于处理数据。
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
const int c_version = 1;
struct RecordHeader
{
int length;
int version;
};
void WriteMyDataFile(char* recordFile, char* data)
{
ofstream output (recordFile, ios::out | ios::binary);
stringstream records(data);
while(records)
{
string r;
records >> r;
if(r.length() < 1)
{
continue;
}
RecordHeader header;
header.length = r.length();
header.version = c_version;
output.write((char*)&header, sizeof(header));
output.write(r.data(), header.length);
}
output.close();
}
vector<string> ReadDataFile(char* recordFile)
{
vector<string> records;
ifstream input (recordFile, ios::in | ios::binary);
while(!input.eof())
{
RecordHeader header;
input.read((char*)&header, sizeof(header));
if(!input.eof())
{
char* buffer = new char[header.length + 1];
input.read(buffer, header.length);
buffer[header.length] = '\0';
string s(buffer);
records.push_back(s);
delete[] buffer;
}
}
return records;
}
int main(int argc, char *argv[])
{
WriteMyDataFile(argv[1], argv[2]);
vector<string> records = ReadDataFile(argv[1]);
for(int i=0; i < records.size(); i++)
{
cout << records[i] << endl;
}
return 0;
}
运行此:
C:\>RecordUtility.exe test.bin "alpha bravo charlie delta"
输出:
阿尔法
布拉沃
查理
delta
答案 0 :(得分:3)
我会通过为您的记录类型重载operator>>
和operator<<
来解决此问题:
struct Record {
struct header {
int length;
int version;
}
header h;
std::vector<char> body;
};
std::istream &operator>>(std::istream &is, Record &r) {
is.read((char *)&r.h, sizeof(r.h));
body.resize(h.length);
is.read(&body[0], h.length);
return is;
}
std::ostream &operator<<(std::ostream &os, Record const &r) {
os.write((char *)r.h, sizeof(r.h));
os.write(r.body, r.body.size());
return OS;
}
完成后,您可以将istream_iterator
和ostream_iterator
与这些结构的流一起使用。例如,要复制大致相当于上述内容的副本,就像:
std::ifstream in("some input file");
std::copy(std::istream_iterator<Record>(in),
std::istream_iterator<Record>(),
std::ostream_iterator<Record>(std::cout, "\n"));
或者,例如,如果您只想复制版本号为2或更高的记录,您可以执行以下操作:
struct filter { // or use a lambda in C++0x
bool operator()(Record const &r) { return r.h.Version < 2; }
};
std::remove_copy_if(std::istream_iterator<Record>(in),
std::istream_iterator<Record>(),
std::ostream_iterator<Record>(std::cout, "\n"),
filter());
答案 1 :(得分:2)
而不是:
while(!input.eof())
写起来更容易(也更清晰):
RecordHeader header;
while(input.read((char*)&header, sizeof(header)))
{
要做模板魔术你想要的是我们std :: istream_iterator和std :: ostream_iterator。
这基本上要求你编写运算符&gt;&gt;和运算符&lt;&lt;为你的班级。
PS。我讨厌使用二进制对象(RecordHeader)。它使代码更难维护。流式传输对象,使其知道如何重新读回自己。这导致回到运算符&gt;&gt;和&lt;&lt;
答案 2 :(得分:1)
您发布的代码以及您对包装类的看法,对我来说,这似乎是使用STL执行此操作的最佳方式。
如果要向主程序提供纯数据,可以查看boost::iostream
。它提供了一些很好的方法来实现过滤器“进入”流(例如zlib
过滤器)以及您正在寻找的内容。
答案 3 :(得分:0)
您可以创建自己的流操作符operator<<
和operator>>
来管理从流中读取/写入Record
结构。然后你可以通过你的记录向量运行事物,应用你想要的任何过滤(可能用std::remove_if
作为问题中的例子)并将其写回类似于下面...
#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>
#include <stdexcept>
#include <sstream>
namespace {
template <class Type>
void WriteBinary(const Type& data, std::ostream& os)
{
const char *binaryData = reinterpret_cast<const char*>(&data);
os.write(binaryData, sizeof(data));
}
template <class Type>
Type ReadBinary(std::istream& is)
{
Type data;
is.read(reinterpret_cast<char*>(&data), sizeof(data));
return data;
}
}
struct Record
{
int mVersion;
std::vector<char> mData;
};
std::ostream& operator<<(std::ostream& os, const Record& record)
{
WriteBinary(record.mData.size(), os);
WriteBinary(record.mVersion, os);
std::copy(record.mData.begin(),
record.mData.end(),
std::ostream_iterator<char>(os));
return os;
}
std::istream& operator>>(std::istream& is, Record& record)
{
if (std::char_traits<char>::not_eof(is.peek()))
{
typedef std::vector<char>::size_type size_type;
size_type length = ReadBinary<size_type>(is);
record.mVersion = ReadBinary<int>(is);
if (record.mVersion != 1)
{
throw std::runtime_error("Invalid version number.");
}
record.mData.clear();
record.mData.resize(length);
is.read(&record.mData.front(), length);
}
else
{
// Read the EOF char to invalidate the stream.
is.ignore();
}
return is;
}
int main()
{
// Create a Record
std::string str = "Hello";
Record rec;
rec.mVersion = 1;
rec.mData.assign(str.begin(), str.end());
// Write two copies of the record to the stream.
std::stringstream ss;
ss << rec << rec;
// Read all the records in the "file"
std::vector<Record> records((std::istream_iterator<Record>(ss)),
std::istream_iterator<Record>());
std::cout << "Read " << records.size() << " records." << std::endl;
// Manipulate records here...then write all of them back to a file.
std::stringstream myNewFile;
std::copy(records.begin(),
records.end(),
std::ostream_iterator<Record>(myNewFile));
return 0;
}