Присоединение к двоичному файлу после записи в начало - PullRequest
0 голосов
/ 11 января 2019

Я создаю двоичный файл записей в следующем формате:

quantity-of-records  
record_1  
record_2  
...  
record_N  

Проблема в том, что record_1 перезаписывается каждый раз вместо добавления.

Запись в EOF после записи в BOF

Вот мой упрощенный код:

#include <fstream>
#include <string>

struct Record
{
    unsigned int    id;
    std::string     text;
};


int main()
{
    static const Record     table[] =
    {
        {
            1, "Apple"
        },
        {
            2, "Salt"
        },
        {
            3, "Margarine"
        },
        {
            4, "Carrot"
        },
        {
            5, "Plum"
        }
    };

    static const size_t records_in_table =
        sizeof(table) / sizeof(table[0]);

    static const char   table_filename[] = "record_file.bin";

    size_t i;
    size_t record_quantity = 1u;
    for (i = 0u; i < records_in_table; ++i)
    {
        std::ofstream   table_file(table_filename,
                                   std::ios::binary);
        table_file.seekp(0, std::ios::beg);
        table_file.write(reinterpret_cast<char *>(&record_quantity),
                         sizeof(record_quantity));
        table_file.flush();

        table_file.seekp(0, std::ios::end);
        table_file.write(reinterpret_cast<char const *>(&table[i].id),
                         sizeof(Record::id));
        const size_t length(table[i].text.length());
        table_file.write(reinterpret_cast<char const *>(&length),
                         sizeof(length));
        table_file.write(table[i].text.c_str(),
                         length);
        table_file.close();
        ++record_quantity;
    }
    return 0;
}

Вот содержимое двоичного файла:

$ od -Ax -x record_file.bin
000000 0005 0000 0000 0000 0005 0000 0004 0000
000010 0000 0000 6c50 6d75
000018

Числа записаны в формате Little Endian, 32-разрядный (4 байта) 64-разрядный (8 байтов). Текст «Слива» в кодировке ASCII: 0x50, 0x6C, 0x75, 0x6D

Вот бинарный файл после первой итерации:

$ od -Ax -x record_file.bin
000000 0001 0000 0000 0000 0001 0000 0005 0000
000010 0000 0000 7041 6c70 0065
000019

Окружающая среда / Инструменты:

  • Компиляторы: Visual Studio 2017, G ++ (GCC) 7.4.0 (Cygwin)
  • ОС: Windows 7

Открытие в режиме app

Альтернативой является открытие файла в режиме ios::app, запись новой записи и обновление количества записей:

size_t  i;
size_t  record_quantity = 1u;
bool    first_write(true);
for (i = 0u; i < records_in_table; ++i)
{
    std::ofstream   table_file(table_filename,
                               std::ios::binary | std::ios::app);
    if (first_write)
    {
        first_write = false;
        table_file.write(reinterpret_cast<char *>(&record_quantity),
                         sizeof(record_quantity));
        table_file.flush();
        table_file.write(reinterpret_cast<char const *>(&table[i].id),
                         sizeof(Record::id));
        const size_t length(table[i].text.length());
        table_file.write(reinterpret_cast<char const *>(&length),
                         sizeof(length));
        table_file.write(table[i].text.c_str(),
                         length);
    }
    else
    {
        table_file.write(reinterpret_cast<char const *>(&table[i].id),
                         sizeof(Record::id));
        const size_t length(table[i].text.length());
        table_file.write(reinterpret_cast<char const *>(&length),
                         sizeof(length));
        table_file.write(table[i].text.c_str(),
                         length);
        table_file.flush();
        table_file.seekp(0, std::ios::beg);
        table_file.write(reinterpret_cast<char *>(&record_quantity),
                         sizeof(record_quantity));
    }
    table_file.close();
    ++record_quantity;
}

Однако в альтернативной реализации количество записей или первое целое число в файле не обновляется.
Вот содержимое двоичного файла:

$ od -Ax -x record_file.bin
000000 0001 0000 0000 0000 0001 0000 0005 0000
000010 0000 0000 7041 6c70 0165 0000 0000 0000
000020 0100 0000 0500 0000 0000 0000 4100 7070
000030 656c 0002 0000 0004 0000 0000 0000 6153
000040 746c 0002 0000 0000 0000 0003 0000 0009
000050 0000 0000 0000 614d 6772 7261 6e69 0365
000060 0000 0000 0000 0400 0000 0600 0000 0000
000070 0000 4300 7261 6f72 0474 0000 0000 0000
000080 0500 0000 0400 0000 0000 0000 5000 756c
000090 056d 0000 0000 0000 0000
000099

Вопрос: Как добавить запись в конец файла и обновить первое целое число (в начале файла)?

Ответы [ 2 ]

0 голосов
/ 11 января 2019

первопричина

Основной причиной или проблемой является режим, в котором файл открывается. Мои эксперименты показывают, что данные добавляются только тогда, когда файл открывается с std::ios_base::app. Тем не менее, большая часть документации подразумевает, что все записи будут добавлены в файл. Если искать позицию, то при записи все равно будут записаны данные в EOF.

Для записи в начало файла без усечения необходимо открыть ofstream с атрибутами std::ios_base::in и std::ios_base::out.

Исправленная программа

Я изменил свою программу так, чтобы записи располагались на границе 16 байт, а неиспользуемые байты заполнялись 0xFF (это облегчает чтение шестнадцатеричного дампа). Все целочисленные данные являются 32-битными; Текст переменной длины.

Сначала записываются данные записи для добавления в файл. Файл открывается дважды с использованием двух разных переменных, по одному в каждом режиме.

#include <fstream>
#include <string>

struct Table_Quantity_Record
{
    unsigned int    quantity;
    uint8_t         padding[12];
};

struct Record
{
    unsigned int    id;
    std::string     text;
};


int main()
{
    static const Record     table[] =
    {
        { 0x11111111, "Apple"},
        { 0x22222222, "Salt"},
        { 0x33333333, "Butter"},
        { 0x44444444, "Carrot"},
        { 0x55555555, "Plum"},
    };

    static const size_t records_in_table =
        sizeof(table) / sizeof(table[0]);

    static const char   table_filename[] = "record_file.bin";

    std::remove(&table_filename[0]);

    size_t  i;
    Table_Quantity_Record   quantity_record;
    quantity_record.quantity = 1;
    std::fill(&quantity_record.padding[0],
              &quantity_record.padding[12],
              0xffu);
    static const uint8_t    padding_bytes[16] = {0xFFu};
    for (i = 0; i < records_in_table; ++i)
    {
        // Open the file in append mode, and append the new data record.
        std::ofstream   data_file(&table_filename[0],
                                  std::ios_base::binary | std::ios_base::app | std::ios_base::ate);
        if (data_file)
        {
            data_file.write((char *) &table[i].id, sizeof(Record::id));
            const unsigned int length = table[i].text.length();
            data_file.write((char *) &length, sizeof(length));
            data_file.write(table[i].text.c_str(), length);
            data_file.flush();
            const unsigned int padding_qty =
                16 - sizeof(Record::id) - sizeof(length) - length;
            static const uint8_t pad_byte = 0xFFU;
            for (size_t j = 0; j < padding_qty; ++j)
            {
                data_file.write((char *) &pad_byte, sizeof(pad_byte));
            }
            data_file.flush();
            data_file.close();
        }

        // Open the data file with "in" attribute to write the record quantity
        // at the beginning of the file.
        std::ofstream   table_file(&table_filename[0],
                                   std::ios_base::binary | std::ios_base::in);
        table_file.write((char *) &quantity_record, sizeof(quantity_record));
        table_file.flush();
        table_file.close();
        ++quantity_record.quantity;
    }
    return 0;
}

Содержимое двоичного файла

$ od -Ax -x record_file.bin
000000 0005 0000 ffff ffff ffff ffff ffff ffff
000010 2222 2222 0004 0000 6153 746c ffff ffff
000020 3333 3333 0006 0000 7542 7474 7265 ffff
000030 4444 4444 0006 0000 6143 7272 746f ffff
000040 5555 5555 0004 0000 6c50 6d75 ffff ffff
000050

enter image description here

Примечание: значения идентификатора записи изменились со времени программы в вопросе, чтобы облегчить поиск записей.

0 голосов
/ 11 января 2019

Проблема в том, как вы открываете поток файлов table_file. Поскольку вы открываете его только для вывода, существующее содержимое файла уничтожается в режиме открытия ios:trunc или без него.

Чтобы добавить к существующему контенту, вам необходимо включить ios:::read как часть вызова:

std::ofstream table_file(table_filename, std::ios::binary | std::ios::in);

(ofstream добавит необходимый флаг std::ios::write.) Хотя это может показаться не интуитивным, требовать режима чтения, когда вы просто записываете в файл, всякий раз, когда вам нужно записать в середину В файле некоторые из существующих материалов обычно необходимо прочитать, так как ваши записи, вероятно, не совпадают с границами хранилища устройства (сектор или кластер).

...