【问题标题】:Appending to binary file after writing to beginning写入开头后附加到二进制文件
【发布时间】:2019-01-10 23:01:50
【问题描述】:

我正在创建具有以下格式的记录的二进制文件:

quantity-of-records  
record_1  
record_2  
...  
record_N  

问题是record_1 每次都被覆盖,而不是追加。

在 BOF 写入后在 EOF 写入

这是我的简化代码:

#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 字节)。 文本“Plum”的 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

问题:如何将记录附加到文件末尾并更新第一个整数(在文件开头)?

【问题讨论】:

    标签: c++ file-io binaryfiles ofstream random-access


    【解决方案1】:

    根本原因

    根本原因或问题是打开文件的模式。我的实验表明,只有在使用std::ios_base::app 打开文件时才会附加数据。但是,大多数文档都暗示 all 写入将附加到文件中。寻找一个位置,然后写入仍然会在 EOF 处写入数据。

    为了在文件的开头写入而不被截断,ofstream 必须使用std::ios_base::instd::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
    

    注意:自问题中的程序以来,记录 ID 值已更改,以便于查找记录。

    【讨论】:

    • 我还添加了对 std::remove 的调用,以确保在每次运行后创建输出文件(以防止覆盖问题)。
    【解决方案2】:

    问题在于您如何open 文件流table_file。由于您只是为了输出而打开它,因此无论是否使用ios:trunc 打开模式,都会破坏现有文件内容。

    要添加到现有内容,您需要在调用中包含ios:::read

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

    ofstream 将添加所需的 std::ios::write 标志。)虽然当您只是写入文件时要求读取模式似乎不直观,但每当您需要写入文件的中间时文件中的一些现有内容通常需要读入,因为您的写入可能无法在设备存储边界(扇区或集群)上很好地对齐。

    【讨论】:

    • 抱歉,G++ 给了我一个错误:read 不是std::ios 的成员。当我使用fstream 时,不会创建文件。见ios::openmode
    • 当我使用std::ofstream table_file(table_filename, std::ios_base::binary | std::ios_base::in)时,文件没有被创建。
    猜你喜欢
    • 2020-10-07
    • 2017-06-30
    • 1970-01-01
    • 1970-01-01
    • 2018-01-13
    • 2017-05-12
    • 1970-01-01
    相关资源
    最近更新 更多