c++ реализация контейнера с итератором для доступа к данным, хранящимся в файле

152
21 января 2019, 17:40

Поскольку когда мучаю читателей этого сайта мелкими задачами и часто возникает вопрос "зачем", то решил описать сразу и большую задачу, с которой и вожусь:

У меня есть данные (в виде файла или группы файлов), которые в бинарном виде содержат последовательность структурированных блоков (можно сказать, что содержат некоторый вектор). Эти файлы огромные (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 и не лучше ли в итератор передавать тогда указатель на класс?

В общем какие в моей идее есть недостатки/ужасные недостатки/отвратительные недостатки?

Answer 1

получился вот такой код

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

Возникло несколько проблем только, решить которые пока не могу:

  1. почему то не удается использовать переопределённый тип 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);
}
  1. Вроде как в классе CExtractorIterator я объявил friend class CExtractor<IData>;, но не получается получить доступ к защищенным методам и членам класса CExtractor(например, к функции update), так что пришлось все сделать публичным, что не очень то хорошо
Answer 2

Исправил все сложности, которые возникли до этого, теперь код стал таким:

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);
}

появился новый вопрос - почему друзей пришлось указать в обоих классах, но это уже не так принципиально и можно постараться будет понять :)

READ ALSO
Помогите составить код с операторами switch,break,goto

Помогите составить код с операторами switch,break,goto

Сейчас делаю задачу из интернета,основная цель используя оператор switch, составить программу для получения таблицы значений функции У для...

173
Вывод имени USB-девайса

Вывод имени USB-девайса

Пытаюсь вывести имя нового USB-девайса, когда его подключаютПроблема в том, что при вставке флешки messagebox появляется, но на нем ничего нет

184
CLion отображение символов

CLion отображение символов

Неправильно отображаются символы в сообщениях об ошибках в терминале CLionСреди руководств в инете полезной информации не нашел, поэтому надеюсь...

144
Меню в консоли через C++

Меню в консоли через C++

заинтересовала одна вещь и возможность ее реализацииХочу создать меню выбора в Командной строке на C++, это должно выглядеть примерно так:

161