Поскольку когда мучаю читателей этого сайта мелкими задачами и часто возникает вопрос "зачем", то решил описать сразу и большую задачу, с которой и вожусь:
У меня есть данные (в виде файла или группы файлов), которые в бинарном виде содержат последовательность структурированных блоков (можно сказать, что содержат некоторый вектор). Эти файлы огромные (100ГБ..1ТБ), поэтому вычитать их целиком в память я не могу.
При этом:
1) есть файлы, содержащие данные одного типа CDataFull
, а есть файлы, содержащие другой тип данных CDataCompact
(я их использую для разных задач, в одном просто больше данных хранится, но данные те же)
2) работа с данными происходит последовательно
Поскольку программа у меня наращивалась постепенно, то появились функции, которые по сути делают одно и тоже, но с разными данными. И поэтому мне захотелось переписать это красиво и компактно.
Сейчас код выглядит примерно так:
bool Process(const std::wstring& path)
{
// получить доступ к файлу с данными и определить его характеристики
HANDLE inputFile = ::CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (inputFile == INVALID_HANDLE_VALUE)
return false;
_i64 fileSize = 0;
const BOOL isGetted = ::GetFileSizeEx(inputFile, (PLARGE_INTEGER)&fileSize);
// выделить память для хранения данных
const _i64 bufferSize = sizeof(CDataCompact) * _BUFFER_RECORDS_AMOUNT_;
char* buffer = new char[bufferSize];
// обработать данные
while (true)
{
// считать кусок файла
DWORD readedBytes = 0;
const BOOL isReaded = ::ReadFile(inputFile, buffer, (DWORD)bufferSize, &readedBytes, NULL);
if (readedBytes == 0)
break;
// последовательно проанализировать записи, записанные в буфер
const _i64 recordsAmount = readedBytes / sizeof(CDataCompact);
for (int recordIndex = 0; recordIndex < recordsAmount; recordIndex++)
{
// распарсить
CData data = parse(buffer + recordIndex * sizeof(CDataCompact));
// обработать данные
ProcessData(data);
}
}
// освободить память
delete[] buffer;
// освободить файл с данными
::CloseHandle(inputFile);
return true;
}
Ну WinAPI работу с файлом я заменил уже на std::ifstream
для однообразия (чтоб STL повсюду).
И решил сделать так, чтобы всю обработку можно было бы реализовать через единый шаблонный класс и итератор, т.е. чтоб выглядело все так:
bool Process(const std::wstring& path)
{
CExtractor<CDataCompact> extractor(path);
for (CExtractor<CDataCompact>::const_iterator it = extractor.begin(); it != extractor.end(); it++)
{
// обработать данные
ProcessData(*it);
}
extractor.close();
return true;
}
Маппить файл тут не вариант - медленная работа да и все равно на него будет нужна обвеска, поэтому решил все свое :)
Т.е. нужно было бы сделать несколько вещей
1) класс, который будет в себе хранить данные о работе с файлом и итераторами "движения по нему"
2) класс, который будет подаваться на вход первому классу, который знает о CDataCompact
, знает его размер и умеет с ним работать (распорсивать)
2.1) а если понадобится, то такой же класс для CDataFull
В результате получился вот такой каркас:
template<typename IData>
class CExtractorIterator : public std::iterator<std::input_iterator_tag, IData>
{
friend class IExtractor;
private:
_i64 m_recordIndex;
private:
CExtractorIterator(const _i64 recordIndex);
public:
CExtractorIterator(const CExtractorIterator &it);
bool operator!=(CExtractorIterator const& other) const;
bool operator==(CExtractorIterator const& other) const;
typename CExtractorIterator::reference operator*() const;
CExtractorIterator& operator++();
};
template<typename IData>
CExtractorIterator<IData>::CExtractorIterator(const _i64 recordIndex)
{
m_recordIndex = recordIndex;
}
template<typename IData>
CExtractorIterator<IData>::CExtractorIterator(const CExtractorIterator& it)
{
m_recordIndex = it.m_recordIndex;
}
template<typename IData>
bool CExtractorIterator<IData>::operator!=(CExtractorIterator const& other) const
{
return m_recordIndex != other.m_recordIndex;
}
template<typename IData>
bool CExtractorIterator<IData>::operator==(CExtractorIterator const& other) const
{
return m_recordIndex == other.m_recordIndex;
}
template<typename IData>
typename CExtractorIterator<IData>::reference CExtractorIterator<IData>::operator*() const
{
// return *p; тут извлечение данных из буфера
}
template<typename IData>
CExtractorIterator<IData> &CExtractorIterator<IData>::operator++()
{
// тут считывание нового куска файла, если происходит выход за буфер
m_recordIndex++;
return *this;
}
template<class IExtractor, class IData>
class CExtractor
{
public:
typedef CExtractorIterator<IData> iterator;
typedef CExtractorIterator<const IData> const_iterator;
protected:
const _i64 m_recordsAmount = 1000000;
std::ifstream m_file;
_i64 m_fileSize = 0;
char* m_fileBuffer = nullptr;
_i64 m_fileBufferSize = 0;
_i64 m_recordsMaxAmount = 0;
public:
CExtractor();
~CExtractor();
bool open(const std::wstring& path);
void close();
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
};
template<class IExtractor, class IData>
CExtractor<IExtractor, IData>::CExtractor
()
{}
template<class IExtractor, class IData>
CExtractor<IExtractor, IData>::~CExtractor
()
{
close();
}
// открыть файл
template<class IExtractor, class IData>
bool
CExtractor<IExtractor, IData>::open
(const std::wstring& path)
{
// закрыть старый файл
close();
// считать данные об IP из файла
m_file.open(path, std::ios::binary | std::ios::ate);
// определить размер файла
m_fileSize = m_file.tellg();
// выставить указатель файла на начало
m_file.seekg(0, std::ios_base::beg);
// выделить память под файловый буфер
m_fileBufferSize = m_recordsAmount * IExtractor::size();
m_fileBuffer = new char[m_fileBufferSize];
// вычислить кол-во записей в файле
m_recordsMaxAmount = m_fileSize / IExtractor::size();
return true;
}
// закрыть файл
template<class IExtractor, class IData>
void
CExtractor<IExtractor, IData>::close
()
{
// освободить память
if (m_fileBuffer != nullptr)
{
delete[] m_fileBuffer;
m_fileBuffer = nullptr;
m_fileBufferSize = 0;
}
// закрыть файл
m_file.close();
}
template<class IExtractor, class IData>
CExtractor<IExtractor, IData>::iterator
CExtractor<IExtractor, IData>::begin
()
{
return iterator(0);
}
template<class IExtractor, class IData>
CExtractor<IExtractor, IData>::const_iterator
CExtractor<IExtractor, IData>::begin() const
{
return const_iterator(0);
}
template<class IExtractor, class IData>
CExtractor<IExtractor, IData>::iterator
CExtractor<IExtractor, IData>::end()
{
return const_iterator(m_recordsMaxAmount);
}
template<class IExtractor, class IData>
CExtractor<IExtractor, IData>::const_iterator
CExtractor<IExtractor, IData>::end() const
{
return const_iterator(m_recordsMaxAmount);
}
И использовать можно так:
CExtractor<CDataCompactParser, CDataCompact> extractor(path);
где CDataCompact
- просто структура, а CDataCompactParser
класс с набором статических функций, который умеет работать (знает размер, распарсивает и т.д.) со структурой CDataCompact
Но теперь встал вопрос - а насколько я большого монстра то придумываю, может и излишним он выходит
1) если CDataCompactParser
знает о CDataCompact
, то пусть он имеет метод для выдачи этого типа, тогда в шаблоне можно избавиться от второго параметры, учитывая, что если во второй параметр ввести не ту структуру, все рухнет, а значит надо защититься от этого
2) где данные о файле должны храниться - в CExtractor
или в CExtractorIterator
и не лучше ли в итератор передавать тогда указатель на класс?
В общем какие в моей идее есть недостатки/ужасные недостатки/отвратительные недостатки?
получился вот такой код
template <typename IData>
class CExtractor;
template <typename IData>
class CExtractorIterator : public std::iterator<std::input_iterator_tag, IData>
{
friend class CExtractor<IData>;
protected:
CExtractor<IData>* m_extractor;
_i64 m_recordIndex;
protected:
CExtractorIterator(CExtractor<IData>* extractor, const _i64 recordIndex);
public:
CExtractorIterator(const CExtractorIterator &it);
bool operator!=(CExtractorIterator const& other) const;
bool operator==(CExtractorIterator const& other) const;
CExtractorIterator& operator++();
typename CExtractorIterator::reference operator*() const;
_i64 pos() const;
};
template <typename IData>
CExtractorIterator<IData>::CExtractorIterator
(CExtractor<IData>* extractor, const _i64 recordIndex)
{
m_recordIndex = recordIndex;
m_extractor = extractor;
}
template <typename IData>
CExtractorIterator<IData>::CExtractorIterator
(const CExtractorIterator& other)
{
m_recordIndex = other.m_recordIndex;
m_extractor = other.m_extractor;
}
template <typename IData>
bool
CExtractorIterator<IData>::operator!=
(CExtractorIterator const& other) const
{
return m_recordIndex != other.m_recordIndex;
}
template <typename IData>
bool
CExtractorIterator<IData>::operator==
(CExtractorIterator const& other) const
{
return m_recordIndex == other.m_recordIndex;
}
template <typename IData>
CExtractorIterator<IData>&
CExtractorIterator<IData>::operator++()
{
m_recordIndex++;
m_extractor->m_localRecordIndex++;
if (m_extractor->m_localRecordIndex >= m_extractor->m_readedRecords)
{
std::cout << m_recordIndex << "\r";
m_extractor->update();
}
return *this;
}
template <typename IData>
typename CExtractorIterator<IData>::reference
CExtractorIterator<IData>::operator*() const
{
return *(IData*)(m_extractor->m_fileBuffer + sizeof(IData) * m_extractor->m_localRecordIndex);
}
template <typename IData>
_i64
CExtractorIterator<IData>::pos() const
{
return m_recordIndex;
}
/* ----------------------------------------------------------------- */
template <typename IData>
class CExtractor
{
public:
typedef CExtractorIterator<IData> iterator;
typedef CExtractorIterator<const IData> const_iterator;
public:
const _i64 m_recordsInBuffer = 1000000;
std::ifstream m_file;
_i64 m_fileSize = 0;
char* m_fileBuffer = nullptr;
_i64 m_fileBufferSize = 0;
_i64 m_recordsInFile = 0;
_i64 m_readedRecords = 0;
_i64 m_localRecordIndex;
protected:
public:
void update();
public:
CExtractor(const std::wstring& path = L"");
~CExtractor();
bool open(const std::wstring& path);
void close();
_i64 size() const;
iterator begin();
iterator end();
const_iterator cbegin() const;
const_iterator cend() const;
};
template <typename IData>
CExtractor<IData>::CExtractor
(const std::wstring& path)
{
if (path.size() > 0)
open(path);
}
template <typename IData>
CExtractor<IData>::~CExtractor
()
{
close();
}
// открыть файл
template <typename IData>
bool
CExtractor<IData>::open
(const std::wstring& path)
{
// закрыть старый файл
close();
// считать данные об IP из файла
m_file.open(path, std::ios::binary | std::ios::ate);
// определить размер файла
m_fileSize = m_file.tellg();
// выставить указатель файла на начало
m_file.seekg(0, std::ios_base::beg);
// выделить память под файловый буфер
m_fileBufferSize = m_recordsInBuffer * sizeof(IData);
m_fileBuffer = new char[m_fileBufferSize];
// вычислить кол-во записей в файле
m_recordsInFile = m_fileSize / sizeof(IData);
// считать первый блок
update();
return true;
}
// закрыть файл
template <typename IData>
void
CExtractor<IData>::close
()
{
// освободить память
if (m_fileBuffer != nullptr)
{
delete[] m_fileBuffer;
m_fileBuffer = nullptr;
m_fileBufferSize = 0;
}
// закрыть файл
m_file.close();
}
// обновить буфер
template <typename IData>
void
CExtractor<IData>::update
()
{
// считать данные из файла в буфер
m_file.read(m_fileBuffer, m_fileBufferSize);
m_readedRecords = m_file.gcount() / sizeof(IData);
m_localRecordIndex = 0;
}
// получить кол-во записей в файле
template <typename IData>
_i64
CExtractor<IData>::size
() const
{
return m_recordsInFile;
}
template<class IData>
CExtractorIterator<IData>
CExtractor<IData>::begin
()
{
return iterator(this, 0);
}
template<class IData>
CExtractorIterator<IData>
CExtractor<IData>::end
()
{
return iterator(this, m_recordsInFile);
}
template<class IData>
CExtractorIterator<const IData>
CExtractor<IData>::cbegin() const
{
return const_iterator(this, 0);
}
template<class IData>
CExtractorIterator<const IData>
CExtractor<IData>::cend() const
{
return const_iterator(this, m_recordsInFile);
}
Оказалось, что не надо указывать второй класс в шаблон, который отвечал для преобразования IData в другой тип
Достаточно просто было для IData переопределить операцию преобразования в нужный тип
operator IData2() const
Возникло несколько проблем только, решить которые пока не могу:
iterator
чтобы вместо
template<class IData>
CExtractorIterator<const IData>
CExtractor<IData>::cend() const
{
return const_iterator(this, m_recordsInFile);
}
было
template<class IData>
CExtractor<IData>::const_iterator
CExtractor<IData>::cend() const
{
return const_iterator(this, m_recordsInFile);
}
friend class CExtractor<IData>;
, но не получается получить доступ к защищенным методам и членам класса CExtractor
(например, к функции update
), так что пришлось все сделать публичным, что не очень то хорошоИсправил все сложности, которые возникли до этого, теперь код стал таким:
template <typename IData>
class CExtractor;
template <typename IData>
class CExtractorIterator : public std::iterator<std::input_iterator_tag, IData>
{
friend class CExtractor<IData>;
protected:
CExtractor<IData>* m_extractor;
_i64 m_recordIndex;
protected:
CExtractorIterator(CExtractor<IData>* extractor, const _i64 recordIndex);
public:
CExtractorIterator(const CExtractorIterator &it);
bool operator!=(CExtractorIterator const& other) const;
bool operator==(CExtractorIterator const& other) const;
CExtractorIterator& operator++();
typename CExtractorIterator::reference operator*() const;
_i64 pos() const;
};
template <typename IData>
CExtractorIterator<IData>::CExtractorIterator
(CExtractor<IData>* extractor, const _i64 recordIndex)
{
m_recordIndex = recordIndex;
m_extractor = extractor;
}
template <typename IData>
CExtractorIterator<IData>::CExtractorIterator
(const CExtractorIterator& other)
{
m_recordIndex = other.m_recordIndex;
m_extractor = other.m_extractor;
}
template <typename IData>
bool
CExtractorIterator<IData>::operator!=
(CExtractorIterator const& other) const
{
return m_recordIndex != other.m_recordIndex;
}
template <typename IData>
bool
CExtractorIterator<IData>::operator==
(CExtractorIterator const& other) const
{
return m_recordIndex == other.m_recordIndex;
}
template <typename IData>
CExtractorIterator<IData>&
CExtractorIterator<IData>::operator++()
{
m_recordIndex++;
m_extractor->m_localRecordIndex++;
if (m_extractor->m_localRecordIndex >= m_extractor->m_readedRecords)
{
std::cout << m_recordIndex << "\r";
m_extractor->update();
}
return *this;
}
template <typename IData>
typename CExtractorIterator<IData>::reference
CExtractorIterator<IData>::operator*() const
{
return *(IData*)(m_extractor->m_fileBuffer + sizeof(IData) * m_extractor->m_localRecordIndex);
}
template <typename IData>
_i64
CExtractorIterator<IData>::pos() const
{
return m_recordIndex;
}
/* ----------------------------------------------------------------- */
template <typename IData>
class CExtractor
{
friend class CExtractorIterator<IData>;
friend class CExtractorIterator<const IData>;
public:
typedef CExtractorIterator<IData> iterator;
typedef CExtractorIterator<const IData> const_iterator;
protected:
const _i64 m_recordsInBuffer = 1000000;
std::ifstream m_file;
_i64 m_fileSize = 0;
char* m_fileBuffer = nullptr;
_i64 m_fileBufferSize = 0;
_i64 m_recordsInFile = 0;
_i64 m_readedRecords = 0;
_i64 m_localRecordIndex;
protected:
void update();
public:
CExtractor(const std::wstring& path = L"");
~CExtractor();
bool open(const std::wstring& path);
void close();
_i64 size() const;
iterator begin();
iterator end();
const_iterator cbegin() const;
const_iterator cend() const;
};
template <typename IData>
CExtractor<IData>::CExtractor
(const std::wstring& path)
{
if (path.size() > 0)
open(path);
}
template <typename IData>
CExtractor<IData>::~CExtractor
()
{
close();
}
// открыть файл
template <typename IData>
bool
CExtractor<IData>::open
(const std::wstring& path)
{
// закрыть старый файл
close();
// считать данные об IP из файла
m_file.open(path, std::ios::binary | std::ios::ate);
// определить размер файла
m_fileSize = m_file.tellg();
// выставить указатель файла на начало
m_file.seekg(0, std::ios_base::beg);
// выделить память под файловый буфер
m_fileBufferSize = m_recordsInBuffer * sizeof(IData);
m_fileBuffer = new char[m_fileBufferSize];
// вычислить кол-во записей в файле
m_recordsInFile = m_fileSize / sizeof(IData);
// считать первый блок
update();
return true;
}
// закрыть файл
template <typename IData>
void
CExtractor<IData>::close
()
{
// освободить память
if (m_fileBuffer != nullptr)
{
delete[] m_fileBuffer;
m_fileBuffer = nullptr;
m_fileBufferSize = 0;
}
// закрыть файл
m_file.close();
}
// обновить буфер
template <typename IData>
void
CExtractor<IData>::update
()
{
// считать данные из файла в буфер
m_file.read(m_fileBuffer, m_fileBufferSize);
m_readedRecords = m_file.gcount() / sizeof(IData);
m_localRecordIndex = 0;
}
// получить кол-во записей в файле
template <typename IData>
_i64
CExtractor<IData>::size
() const
{
return m_recordsInFile;
}
template<class IData>
typename CExtractor<IData>::iterator
CExtractor<IData>::begin
()
{
return iterator(this, 0);
}
template<class IData>
typename CExtractor<IData>::iterator
CExtractor<IData>::end
()
{
return iterator(this, m_recordsInFile);
}
template<class IData>
typename CExtractor<IData>::const_iterator
CExtractor<IData>::cbegin() const
{
return const_iterator(this, 0);
}
template<class IData>
typename CExtractor<IData>::const_iterator
CExtractor<IData>::cend() const
{
return const_iterator(this, m_recordsInFile);
}
появился новый вопрос - почему друзей пришлось указать в обоих классах, но это уже не так принципиально и можно постараться будет понять :)
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Сейчас делаю задачу из интернета,основная цель используя оператор switch, составить программу для получения таблицы значений функции У для...
Пытаюсь вывести имя нового USB-девайса, когда его подключаютПроблема в том, что при вставке флешки messagebox появляется, но на нем ничего нет
Неправильно отображаются символы в сообщениях об ошибках в терминале CLionСреди руководств в инете полезной информации не нашел, поэтому надеюсь...
заинтересовала одна вещь и возможность ее реализацииХочу создать меню выбора в Командной строке на C++, это должно выглядеть примерно так: