I have a binary file that i write some struct items to it. Now I want to find and update specific item from file items.
Note that my struct has a vector and its size is not constant.
my struct:
struct mapItem
{
string term;
vector<int> pl;
};
codes that write struct items to file
if (it==hashTable.end())//didn't find
{
vector <int> posting;
posting.push_back(position);
hashTable.insert ( pair<string,vector <int> >(md,posting ) );
mapItem* mi = new mapItem();
mi->term = md;
mi->pl = posting;
outfile.write((char*)mi, sizeof(mi));
}
else//finded
{
}
In else block I want to find and update item with its term(term is unique).
Now I have changed my code like this to serialize my vector.
if (it==hashTable.end())//didn't find
{
vector <int> posting;
posting.push_back(position);
hashTable.insert ( pair<string,vector <int> >(md,posting ) );
mapItem* mi = new mapItem();
mi->term = md;
mi->pl = posting;
if(!outfile.is_open())
outfile.open("sample.dat", ios::binary | ios::app);
size_t size = mi->term.size() + 1;
outfile.write((char*)&size, sizeof(size) );
outfile.write((char*)mi->term.c_str(), size);
size = (int)mi->pl.size() * sizeof(int);
outfile.write((char*)&size, sizeof(size) );
outfile.write((char*)&mi->pl[0], size );
outfile.close();
}
else//finded
{
(it->second).push_back(position);
mapItem* mi = new mapItem();
size_t size;
if(!infile.is_open())
{
infile.open("sample.dat", ios::binary | ios::in);
}
do{
infile.read((char*)&size, sizeof(size) ); // string size
mi->term.resize(size - 1); // make string the right size
infile.read((char*)mi->term.c_str(), size); // may need const_cast
infile.read((char*)&size, sizeof(size) ); // vector size
mi->pl.resize(size / sizeof(int));
infile.read((char*)&mi->pl[0], size );
}while(mi->term != md);
infile.close();
}
Well, my main question still remains: how can I update the data that I found?
Is there a better way to find them?
I evaluated the following solutions:
update in a new file, rename it to the old one in the end
update in the same file with a stream with two file positions, read & write, but I didn't rapidly find support for such a thing
update in the same file with two streams, read & write, but the risk of underlying overwrite is too big for me (even if protected outside against overlaps)
So I choose the first one, the most straightforward anyway.
#include <string>
#include <vector>
#include <fstream>
#include <cstdio>
#include <assert.h>
I added the following function to your struct:
size_t SizeWrittenToFile() const
{
return 2*sizeof(size_t)+term.length()+pl.size()*sizeof(int);
}
The read & write functions are basically same as your, except I choose not to write to string:c_str() pointer (although this ugly solution should work on every known compiles).
bool ReadNext(std::istream& in, mapItem& item)
{
size_t size;
in.read(reinterpret_cast<char*>(&size), sizeof(size_t));
if (!in)
return false;
std::istreambuf_iterator<char> itIn(in);
std::string& out = item.term;
out.reserve(size);
out.clear(); // this is necessary if the string is not empty
for (std::insert_iterator<std::string> itOut(out, out.begin());
in && (out.length() < size); itIn++, itOut++)
*itOut = *itIn;
assert(in);
if (!in)
return false;
in.read(reinterpret_cast<char*>(&size), sizeof(size_t));
if (!in)
return false;
std::vector<int>& out2 = item.pl;
out2.resize(size); // unfortunately reserve doesn't work here
in.read(reinterpret_cast<char*>(&out2[0]), size * sizeof(int));
assert(in);
return true;
}
// a "header" should be added to mark complete data (to write "atomically")
bool WriteNext(std::ostream& out, const mapItem& item)
{
size_t size = item.term.length();
out.write(reinterpret_cast<const char*>(&size), sizeof(size_t));
if (!out)
return false;
out.write(item.term.c_str(), size);
if (!out)
return false;
size = item.pl.size();
out.write(reinterpret_cast<const char*>(&size), sizeof(size_t));
if (!out)
return false;
out.write(reinterpret_cast<const char*>(&item.pl[0]), size * sizeof(int));
if (!out)
return false;
return true;
}
The update functions look like this:
bool UpdateItem(std::ifstream& in, std::ofstream& out, const mapItem& item)
{
mapItem it;
bool result;
for (result = ReadNext(in, it); result && (it.term != item.term);
result = ReadNext(in, it))
if (!WriteNext(out, it))
return false;
if (!result)
return false;
// write the new item content
assert(it.term == item.term);
if (!WriteNext(out, item))
return false;
for (result = ReadNext(in, it); result; result = ReadNext(in, it))
if (!WriteNext(out, it))
return false;
// failure or just the end of the file?
return in.eof();
}
bool UpdateItem(const char* filename, const mapItem& item)
{
std::ifstream in(filename);
assert(in);
std::string filename2(filename);
filename2 += ".tmp";
std::ofstream out(filename2.c_str());
assert(out);
bool result = UpdateItem(in, out, item);
// close them before delete
in.close();
out.close();
int err = 0;
if (result)
{
err = remove(filename);
assert(!err && "remov_140");
result = !err;
}
if (!result)
{
err = remove(filename2.c_str());
assert(!err && "remov_147");
}
else
{
err = rename(filename2.c_str(), filename);
assert(!err && "renam_151");
result = !err;
}
return result;
}
Questions ?
This:
outfile.write((char*)mi, sizeof(mi));
Does not make sense. You're writing the bits of a vector's implementation directly to disk. Some of those bits are extremely likely to be pointers. Pointers written to a file on disk are not useful, because they point to an address space belonging to the process which wrote the file, but won't work in another process reading the same file.
You need to "serialize" your data to the file, e.g. in a for loop writing each element.
You can serialize the struct to a file this way:
write length of string (4 bytes)
write string itself.
write length of vector (in bytes is easier to parse later).
write vector data. &vec[0] is the address of the first element. you can write all elements in ones shot since this buffer is contiguous.
Write:
size_t size = mi->term.size() + 1;
outfile.write((char*)&size, sizeof(size) );
outfile.write((char*)mi->term.c_str(), size);
size = (int)mi->pl.size() * sizeof(int);
outfile.write((char*)&size, sizeof(size) );
outfile.write((char*)&mi->pl[0], size );
Read:
infile.read((char*)&size, sizeof(size) ); // string size
mi->term.resize(size - 1); // make string the right size
infile.read((char*)mi->term.c_str(), size); // may need const_cast
infile.read((char*)&size, sizeof(size) ); // vector size
mi->pl.resize(size / sizeof(int));
infile.read((char*)&mi->pl[0], size );
Related
Based on this this question:
How to read a binary file into a vector of unsigned chars
In the answer they have:
std::vector<BYTE> readFile(const char* filename)
{
// open the file:
std::basic_ifstream<BYTE> file(filename, std::ios::binary);
// read the data:
return std::vector<BYTE>((std::istreambuf_iterator<BYTE>(file)),
std::istreambuf_iterator<BYTE>());
}
Which reads the entire file into the vector.
What I want to do is read (for example) 100 bytes at a time in the vector, then do stuff, and then read the next 100 bytes into the vector (clear the vector between). I don't see how to specify how much of the file to read (i.e. how to setup the iterators). Is that even possible?
I am trying to avoid having to write my own code loop to copy each byte at a time.
You can use ifstream::read for that.
std::vector<BYTE> v(100);
while ( file.read(reinterpret_cast<char*>(v.data()), 100) )
{
// Find out how many characters were actually read.
auto count = file.gcount();
// Use v up to count BTYEs.
}
You could write a function to:
void readFile( const std::string &fileName, size_t chunk, std::function<void(const std::vector<BYTE>&)> proc )
{
std::ifstream f( fileName );
std::vector<BYTE> v(chunk);
while( f.read( v.data(), v.size() ) ) {
v.resize( f.gcount() );
proc( v );
v.resize( chunk );
}
}
then usage is simple:
void process( const std::vector<BYTE> &v ) { ... }
readFile( "foobar", 100, process ); // call process for every 100 bytes of data
or you can use lambda etc for callback.
Or you can write your own function for that:
template<typename Data>
std::istreambuf_iterator<Data> readChunk(std::istreambuf_iterator<Data>& curr, std::vector<Data>& vec, size_t chunk = 100) {
for (int i = 0; curr != std::istreambuf_iterator<Data>() && i < chunk; ++i, ++curr) {
vec.emplace_back(*curr);
}
return curr;
}
and use it as:
std::ifstream file("test.cpp");
std::vector<BYTE> v;
std::istreambuf_iterator<BYTE> curr(file);
readChunk<BYTE>(curr, v);
And you can call this function again.
I'm trying to read a txt file, and put it into an char array. But can I read different files which contain different length of characters and put them into an array. Can I create a dynamic array to contain unknown length of characters.
You can read a file of unknown size into a dynamics data structure like:
std::vector More info here.
Alternatively, you can use new to allocate a dynamic memory. However, vectors are more convenient at least to me :).
#include <vector>
#include <iostream>
#include <fstream>
int main(int argc, char **argv)
{
std::vector<std::string> content;
if (argc != 2)
{
std::cout << "bad argument" << std::endl;
return 0;
}
std::string file_name (argv[1]);
std::ifstream file(file_name);
if (!file)
{
std::cout << "can't open file" << std::endl;
return 0;
}
std::string line = "";
while (std::getline(file, line))
{
content.push_back(line);
line = "";
}
for (std::vector<std::string>::iterator it = content.begin(); it != content.end(); ++it)
std::cout << *it << std::endl;
}
here is a solution using std::vectors and std::string
the programm takes a file name as first parameter, opens it, read it line by line
each line is written in the vector
then you can display your vector as i did at the end of the function
EDIT: because C++11 is the new standars, the program use C++11 then you have to compile it using c++11 (g++ -std=c++11 if you use g++)
I just tested it it works perfectly
There may be library routines available which give you the size of the file without reading the contents of the file. In that case you could get the size and allocate a full-sized buffer, and suck in the whole file at once [if your buffer is a simple char array, don't forget to add one and put in the trailing nullchar].
The best way is use of malloc(), realloc(), and free() just like it was an old C program. If you try to use a std::vector you will choke approaching maximum RAM as realloc() can grow and shrink in place (grow is contingent on heap while shrink is guaranteed to work) while std::vector cannot do so.
In particular:
#include <iostream>
#include <tuple>
// TODO perhaps you want an auto-management class for this tuple.
// I'm not about to type up the whole auto_ptr, shared_ptr, etc.
// Mostly you don't do this enough to have to worry too hard.
std::tuple<char *, size_t> getarray(std::istream &input)
{
size_t fsize = 0;
size_t asize = 0;
size_t offset = 0;
size_t terminator = (size_t)-1;
char *buf = malloc(asize = 8192);
if (!buf) throw std::bad_alloc();
char *btmp;
char shift = 1;
do {
try {
input.read(buf + offset, asize - fsize);
} catch (...) {
free(buf);
throw;
}
if (input.gcount == 0) {
btmp = realloc(buf, bufsize);
if (btmp) buf = btmp;
return std::tuple<char *, size_t>(buf, fsize);
}
offset += input.gcount;
fsize += offset;
if (fsize == asize) {
if (shift) {
if ((asize << 1) == 0)
shift = 0;
else {
btmp = realloc(buf, asize << 1);
if (!btmp)
shift = 0;
else {
asize <<= 1;
buf = btmp;
}
}
if (!shift) {
btmp = realloc(buf, asize += 8192);
if (!btmp) {
free(buf);
throw std::bad_alloc();
}
}
}
}
} while (terminator - offset > fsize);
free(buf);
// Or perhaps something suitable.
throw "File too big to fit in size_t";
}
Currently I use this template method to read a file and store the content to a std::vector:
template <class T> std::vector<T> FileToBuffer(const std::string pathToFile, std::vector<T> *ignoreList = NULL, bool binary = false)
{
std::vector<T> fileBuffer;
std::ifstream file;
try
{
if(binary) file.open(pathToFile, std::ios::in | ios::binary);
else file.open(pathToFile, std::ios::in);
if (file)
{
// get length of file:
file.seekg (0, file.end);
int length = file.tellg();
file.seekg (0, file.beg);
fileBuffer.resize(length);
file.read((char*)fileBuffer.data(), length);
file.close();
// remove unwanted elements
if(ignoreList != NULL)
{
for(std::vector<T>::iterator it=ignoreList->begin(); it!=ignoreList->end(); it++)
{
fileBuffer.erase(std::remove(fileBuffer.begin(), fileBuffer.end(), *it), fileBuffer.end());
}
}
}
}
catch(std::exception &exp)
{
fileBuffer.clear();
}
return fileBuffer;
}
To filter unwanted elements I go through the whole vector and delete it! Is there a better way to filter the unwanted elements immediately in read process?
(in fact I need this to remove line feed and carriage return)
Thanks!
This should work:
if (file)
{
T object;
while ( file.read((char*)(&object), sizeof(T)) )
{
bool found = false;
if(ignoreList != NULL)
{
auto iter = std::find(ignoreList->begin(), ignoreList->end(), object);
found = (iter != ignoreList->end());
}
if ( !found )
{
fileBuffer.push_back(object);
}
}
file.close();
}
Following is the code for creating a memory map file using boost.
boost::iostreams::mapped_file_source file;
boost::iostreams::mapped_file_params param;
param.path = "\\..\\points.pts"; //! Filepath
file.open(param, fileSize);
if(file.is_open())
{
//! Access the buffer and populate the ren point buffer
const char* pData = file.data();
char* pData1 = const_cast<char*>(pData); //! this gives me all the data from Mmap file
std::vector<RenPoint> readPoints;
ParseData( pData1, readPoints);
}
The implementation of ParseData is as follows
void ParseData ( char* pbuffer , std::vector<RenPoint>>& readPoints)
{
if(!pbuffer)
throw std::logic_error("no Data in memory mapped file");
stringstream strBuffer;
strBuffer << pbuffer;
//! Get the max number of points in the pts file
std::string strMaxPts;
std::getline(strBuffer,strMaxPts,'\n');
auto nSize = strMaxPts.size();
unsigned nMaxNumPts = GetValue<unsigned>(strMaxPts);
readPoints.clear();
//! Offset buffer
pbuffer += nSize;
strBuffer << pbuffer;
std::string cur_line;
while(std::getline(strBuffer, cur_line,'\n'))
{
//! How do I read the data from mmap file directly and populate my renpoint structure
int yy = 0;
}
//! Working but very slow
/*while (std::getline(strBuffer,strMaxPts,'\n'))
{
std::vector<string> fragments;
istringstream iss(strMaxPts);
copy(istream_iterator<string>(iss),
istream_iterator<string>(),
back_inserter<vector<string>>(fragments));
//! Logic to populate the structure after getting data back from fragments
readPoints.push_back(pt);
}*/
}
I have say a minimum of 1 million points in my data structure and I want to optimize my parsing. Any ideas ?
read in header information to get the number of points
reserve space in a std::vector for N*num_points (N=3 assuming only X,Y,Z, 6 with normals, 9 with normals and rgb)
load the remainder of the file into a string
boost::spirit::qi::phrase_parse into the vector.
//code here can parse a file with 40M points (> 1GB) in about 14s on my 2 year old macbook:
#include <boost/spirit/include/qi.hpp>
#include <fstream>
#include <vector>
template <typename Iter>
bool parse_into_vec(Iter p_it, Iter p_end, std::vector<float>& vf) {
using boost::spirit::qi::phrase_parse;
using boost::spirit::qi::float_;
using boost::spirit::qi::ascii::space;
bool ret = phrase_parse(p_it, p_end, *float_, space, vf);
return p_it != p_end ? false : ret;
}
int main(int argc, char **args) {
if(argc < 2) {
std::cerr << "need a file" << std::endl;
return -1;
}
std::ifstream in(args[1]);
size_t numPoints;
in >> numPoints;
std::istreambuf_iterator<char> eos;
std::istreambuf_iterator<char> it(in);
std::string strver(it, eos);
std::vector<float> vf;
vf.reserve(3 * numPoints);
if(!parse_into_vec(strver.begin(), strver.end(), vf)) {
std::cerr << "failed during parsing" << std::endl;
return -1;
}
return 0;
}
AFAICT, you're currently copying the entire contents of the file into strBuffer.
What I think you want to do is use boost::iostreams::stream with your mapped_file_source instead.
Here's an untested example, based on the linked documentation:
// Create the stream
boost::iostreams::stream<boost::iostreams::mapped_file_source> str("some/path/file");
// Alternately, you can create the mapped_file_source separately and tell the stream to open it (using a copy of your mapped_file_source)
boost::iostreams::stream<boost::iostreams::mapped_file_source> str2;
str2.open(file);
// Now you can use std::getline as you normally would.
std::getline(str, strMaxPts);
As an aside, I'll note that by default mapped_file_source maps the entire file, so there's no need to pass the size explicitly.
You can go with something like this (just a fast concept, you'll need to add some additional error checking etc.):
#include "boost/iostreams/stream.hpp"
#include "boost/iostreams/device/mapped_file.hpp"
#include "boost/filesystem.hpp"
#include "boost/lexical_cast.hpp"
double parse_double(const std::string & str)
{
double value = 0;
bool decimal = false;
double divisor = 1.0;
for (std::string::const_iterator it = str.begin(); it != str.end(); ++it)
{
switch (*it)
{
case '.':
case ',':
decimal = true;
break;
default:
{
const int x = *it - '0';
value = value * 10 + x;
if (decimal)
divisor *= 10;
}
break;
}
}
return value / divisor;
}
void process_value(const bool initialized, const std::string & str, std::vector< double > & values)
{
if (!initialized)
{
// convert the value count and prepare the output vector
const size_t count = boost::lexical_cast< size_t >(str);
values.reserve(count);
}
else
{
// convert the value
//const double value = 0; // ~ 0:20 min
const double value = parse_double(str); // ~ 0:35 min
//const double value = atof(str.c_str()); // ~ 1:20 min
//const double value = boost::lexical_cast< double >(str); // ~ 8:00 min ?!?!?
values.push_back(value);
}
}
bool load_file(const std::string & name, std::vector< double > & values)
{
const int granularity = boost::iostreams::mapped_file_source::alignment();
const boost::uintmax_t chunk_size = ( (256 /* MB */ << 20 ) / granularity ) * granularity;
boost::iostreams::mapped_file_params in_params(name);
in_params.offset = 0;
boost::uintmax_t left = boost::filesystem::file_size(name);
std::string value;
bool whitespace = true;
bool initialized = false;
while (left > 0)
{
in_params.length = static_cast< size_t >(std::min(chunk_size, left));
boost::iostreams::mapped_file_source in(in_params);
if (!in.is_open())
return false;
const boost::iostreams::mapped_file_source::size_type size = in.size();
const char * data = in.data();
for (boost::iostreams::mapped_file_source::size_type i = 0; i < size; ++i, ++data)
{
const char c = *data;
if (strchr(" \t\n\r", c))
{
// c is whitespace
if (!whitespace)
{
whitespace = true;
// finished previous value
process_value(initialized, value, values);
initialized = true;
// start a new value
value.clear();
}
}
else
{
// c is not whitespace
whitespace = false;
// append the char to the value
value += c;
}
}
if (size < chunk_size)
break;
in_params.offset += chunk_size;
left -= chunk_size;
}
if (!whitespace)
{
// convert the last value
process_value(initialized, value, values);
}
return true;
}
Note that your main problem will be the conversion from string to float, which is very slow (insanely slow in the case of boost::lexical_cast). With my custom special parse_double func it is faster, however it only allows a special format (e.g. you'll need to add sign detection if negative values are allowed etc. - or you can just go with atof if all possible formats are needed).
If you'll want to parse the file faster, you'll probably need to go for multithreading - for example one thread only parsing the string values and other one or more threads converting the loaded string values to floats. In that case you probably won't even need the memory mapped file, as the regular buffered file read might suffice (the file will be read only once anyway).
A few quick comments on your code:
1) you're not reserving space for your vector so it's doing expansion every time you add a value. You have read the number of points from the file so call reserve(N) after the clear().
2) you're forcing a map of the entire file in one hit which will work on 64 bits but is probably slow AND is forcing another allocation of the same amount of memory with strBuffer << pbuffer;
http://www.boost.org/doc/libs/1_53_0/doc/html/interprocess/sharedmemorybetweenprocesses.html#interprocess.sharedmemorybetweenprocesses.mapped_file.mapped_file_mapping_regions shows how to getRegion
Use a loop through getRegion to load an estimated chunk of data containing many lines. You are going to have to handle partial buffers - each getRegion will likely end with part of a line you need to preserve and join to the next partial buffer starting the next region.
I'm looking for a way to write floats/ints/strings to a file and read them as floats/ints/strings. (basically read/write as ios::binary).
I ended up writing it myself. Just wanted to share it with others.
It might not be optimized, but I had some difficulties finding C++ code that mimics C#'s BinaryReader & BinaryWriter classes. So I created one class that handles both read and write.
Quick things to note:
1) "BM" is just a prefix for my classes.
2) BMLogging is a helper class that simply does:
cout << "bla bla bla" << endl;
So you can ignore the calls to BMLogging, I kept them to highlight the cases where we could warn the user.
Here's the code:
#include <iostream>
#include <fstream>
using namespace std;
// Create the macro so we don't repeat the code over and over again.
#define BMBINARY_READ(reader,value) reader.read((char *)&value, sizeof(value))
enum BMBinaryIOMode
{
None = 0,
Read,
Write
};
class BMBinaryIO
{
// the output file stream to write onto a file
ofstream writer;
// the input file stream to read from a file
ifstream reader;
// the filepath of the file we're working with
string filePath;
// the current active mode.
BMBinaryIOMode currentMode;
public:
BMBinaryIO()
{
currentMode = BMBinaryIOMode::None;
}
// the destructor will be responsible for checking if we forgot to close
// the file
~BMBinaryIO()
{
if(writer.is_open())
{
BMLogging::error(BMLoggingClass::BinaryIO, "You forgot to call close() after finishing with the file! Closing it...");
writer.close();
}
if(reader.is_open())
{
BMLogging::error(BMLoggingClass::BinaryIO, "You forgot to call close() after finishing with the file! Closing it...");
reader.close();
}
}
// opens a file with either read or write mode. Returns whether
// the open operation was successful
bool open(string fileFullPath, BMBinaryIOMode mode)
{
filePath = fileFullPath;
BMLogging::info(BMLoggingClass::BinaryIO, "Opening file: " + filePath);
// Write mode
if(mode == BMBinaryIOMode::Write)
{
currentMode = mode;
// check if we had a previously opened file to close it
if(writer.is_open())
writer.close();
writer.open(filePath, ios::binary);
if(!writer.is_open())
{
BMLogging::error(BMLoggingClass::BinaryIO, "Could not open file for write: " + filePath);
currentMode = BMBinaryIOMode::None;
}
}
// Read mode
else if(mode == BMBinaryIOMode::Read)
{
currentMode = mode;
// check if we had a previously opened file to close it
if(reader.is_open())
reader.close();
reader.open(filePath, ios::binary);
if(!reader.is_open())
{
BMLogging::error(BMLoggingClass::BinaryIO, "Could not open file for read: " + filePath);
currentMode = BMBinaryIOMode::None;
}
}
// if the mode is still the NONE/initial one -> we failed
return currentMode == BMBinaryIOMode::None ? false : true;
}
// closes the file
void close()
{
if(currentMode == BMBinaryIOMode::Write)
{
writer.close();
}
else if(currentMode == BMBinaryIOMode::Read)
{
reader.close();
}
}
bool checkWritabilityStatus()
{
if(currentMode != BMBinaryIOMode::Write)
{
BMLogging::error(BMLoggingClass::BinaryIO, "Trying to write with a non Writable mode!");
return false;
}
return true;
}
// Generic write method that will write any value to a file (except a string,
// for strings use writeString instead).
void write(void *value, size_t size)
{
if(!checkWritabilityStatus())
return;
// write the value to the file.
writer.write((const char *)value, size);
}
// Writes a string to the file
void writeString(string str)
{
if(!checkWritabilityStatus())
return;
// first add a \0 at the end of the string so we can detect
// the end of string when reading it
str += '\0';
// create char pointer from string.
char* text = (char *)(str.c_str());
// find the length of the string.
unsigned long size = str.size();
// write the whole string including the null.
writer.write((const char *)text, size);
}
// helper to check if we're allowed to read
bool checkReadabilityStatus()
{
if(currentMode != BMBinaryIOMode::Read)
{
BMLogging::error(BMLoggingClass::BinaryIO, "Trying to read with a non Readable mode!");
return false;
}
// check if we hit the end of the file.
if(reader.eof())
{
BMLogging::error(BMLoggingClass::BinaryIO, "Trying to read but reached the end of file!");
reader.close();
currentMode = BMBinaryIOMode::None;
return false;
}
return true;
}
// reads a boolean value
bool readBoolean()
{
if(checkReadabilityStatus())
{
bool value = false;
BMBINARY_READ(reader, value);
return value;
}
return false;
}
// reads a character value
char readChar()
{
if(checkReadabilityStatus())
{
char value = 0;
BMBINARY_READ(reader, value);
return value;
}
return 0;
}
// read an integer value
int readInt()
{
if(checkReadabilityStatus())
{
int value = 0;
BMBINARY_READ(reader, value);
return value;
}
return 0;
}
// read a float value
float readFloat()
{
if(checkReadabilityStatus())
{
float value = 0;
BMBINARY_READ(reader, value);
return value;
}
return 0;
}
// read a double value
double readDouble()
{
if(checkReadabilityStatus())
{
double value = 0;
BMBINARY_READ(reader, value);
return value;
}
return 0;
}
// read a string value
string readString()
{
if(checkReadabilityStatus())
{
char c;
string result = "";
while((c = readChar()) != '\0')
{
result += c;
}
return result;
}
return "";
}
};
EDIT: I replaced all the read/write methods above with these: (updated the usage code as well)
// Generic write method that will write any value to a file (except a string,
// for strings use writeString instead)
template<typename T>
void write(T &value)
{
if(!checkWritabilityStatus())
return;
// write the value to the file.
writer.write((const char *)&value, sizeof(value));
}
// Writes a string to the file
void writeString(string str)
{
if(!checkWritabilityStatus())
return;
// first add a \0 at the end of the string so we can detect
// the end of string when reading it
str += '\0';
// create char pointer from string.
char* text = (char *)(str.c_str());
// find the length of the string.
unsigned long size = str.size();
// write the whole string including the null.
writer.write((const char *)text, size);
}
// reads any type of value except strings.
template<typename T>
T read()
{
checkReadabilityStatus();
T value;
reader.read((char *)&value, sizeof(value));
return value;
}
// reads any type of value except strings.
template<typename T>
void read(T &value)
{
if(checkReadabilityStatus())
{
reader.read((char *)&value, sizeof(value));
}
}
// read a string value
string readString()
{
if(checkReadabilityStatus())
{
char c;
string result = "";
while((c = read<char>()) != '\0')
{
result += c;
}
return result;
}
return "";
}
// read a string value
void readString(string &result)
{
if(checkReadabilityStatus())
{
char c;
result = "";
while((c = read<char>()) != '\0')
{
result += c;
}
}
}
This is how you would use it to WRITE:
string myPath = "somepath to the file";
BMBinaryIO binaryIO;
if(binaryIO.open(myPath, BMBinaryIOMode::Write))
{
float value = 165;
binaryIO.write(value);
char valueC = 'K';
binaryIO.write(valueC);
double valueD = 1231.99;
binaryIO.write(valueD);
string valueStr = "spawnAt(100,200)";
binaryIO.writeString(valueStr);
valueStr = "helpAt(32,3)";
binaryIO.writeString(valueStr);
binaryIO.close();
}
Here's how you would use it to READ:
string myPath = "some path to the same file";
if(binaryIO.open(myPath, BMBinaryIOMode::Read))
{
cout << binaryIO.read<float>() << endl;
cout << binaryIO.read<char>() << endl;
double valueD = 0;
binaryIO.read(valueD); // or you could use read<double()
cout << valueD << endl;
cout << binaryIO.readString() << endl;
cout << binaryIO.readString() << endl;
binaryIO.close();
}
EDIT 2: You could even write/read a whole structure in 1 line:
struct Vertex {
float x, y;
};
Vertex vtx; vtx.x = 2.5f; vtx.y = 10.0f;
// to write it
binaryIO.write(vtx);
// to read it
Vertex vtxRead;
binaryIO.read(vtxRead); // option 1
vtxRead = binaryIO.read<Vertex>(); // option 2
Hope my code is clear enough.
I subclassed ifstream and ofstream: ibfstream and obfstream. I made a little helper class that would detect the endianness of the machine I was compiling/running on. Then I added a flag for ibfstream and obfstream that indicated whether bytes in primitive types should be flipped. These classes also had methods to read/write primitive types and arrays of such types flipping the byte order as necessary. Finally, I set ios::binary for these classes by default.
I was often working on a little-endian machine and wanting to write big-endian files or vice versa. This was used in a program that did a lot of I/O with 3D graphics files of various formats.
I subclassed ifstream and ofstream: ibfstream and obfstream. I made a class that would detect the endianness of the machine I was compiling/running on. Then I added a flag for ibfstream and obfstream that indicated whether bytes in primitive types should be flipped. These classes also had methods to read/write primitive types and arrays of such types flipping the byte order as necessary.
I was often working on a little-endian machine and wanting to write big-endian files or vice versa. This was used in a program tht did a lot of I/O with 3D graphics files of various formats.