C++ 中用于随机 R/W 字节块的基本文件 I/O

Basic file I/O in C++ for random R/W of byte chunks

我正在将一个低级文件 I/O 库从 Java 移植到 C++,我需要一些有关 C++ 中基本文件 I/O 的帮助。当前 API 看起来像:

public class BinaryFile {

    // open/close the file stream
    public BinaryFile(string path, string mode)
    public void Close()

    // append to the end of file
    public void AppendBytes(byte[] bytes, uint readPos, uint length)

    // write a certain byte chunk at a certain position into the file
    public void WriteBytes(byte[] bytes, uint readPos, uint length, uint writePos)

    // read a certain byte chunk from the file
    public byte[] ReadBytes(uint position, uint length)
}

首先,我已经了解了在 C/C++ 中访问 files/file 流的所有 5 种不同方式,我真的不关心我使用哪种方法(fread 和朋友可能没问题)。如您所见,我需要从文件的任何部分随机 read/write 二进制块,因此 fgets 不太合适,因为它写入了长度前缀。

但是,由于我对 C++ 有点陌生,是否有库或头文件已经具有类似的 API? (请不要使用像 boost 这样的整体框架)简而言之,我只需要读取、写入二进制块并将其附加到二进制文件中。没有汗水,没有字符串,没有 JSON,没有 XML,没有什么复杂的。在 VC++ 2010 中实现此目标的最简单方法是什么?我有 Visual Studio 2010.

编辑: 我的目标是 Windows XP+ 并构建一个 DLL,我已经包括 <stdlib.h><stdio.h><windows.h>#define WIN32_LEAN_AND_MEAN.

无论如何,C/C++ 标准库都将文件视为流,而不是 random-access 资源。

您的 class public 部分可能如下所示:

class BinaryFile
{
public:
    BinaryFile(const std::string & path, const std::string & mode);
    ~BinaryFile();

    void AppendBytes(const std::vector<uint8_t> & bytes, size_t readPos, size_t length);

    void WriteBytes(const std::vector<uint8_t> & bytes, size_t readPos, size_t length, size_t writePos);

    std::vector<uint8_t> ReadBytes(size_t position, size_t length);
}

您可以使用 FILE* APIs from <cstdio>:

#include <cstdio>

struct foo {
    unsigned int a;
    unsigned int b;
};

int main(void)
{
    // connect to the file
    FILE *f = fopen("test.bin", "wb");
    if (!f)
        return 1;

    // use "unbuffered mode" since you are doing random access
    setbuf(f, NULL );

    // declare an array of 2 objects
    struct foo data[] = { 
        { .a = 0xDEADBEEF, .b = 0x2B84F00D },
        { .a = 0xCAFEBABE, .b = 0xBAADB0B1 },
    };  

    // write the data
    fwrite(&data, sizeof(struct foo), 2, f); 

    // move to byte 0x20
    fseek(f, 0x20, SEEK_SET);

    // write an ASCII string
    fprintf(f, "ASCII TOO");

    // disconnect from the file
    fclose(f);

    return 0;
}

test.bin 的十六进制转储:

00000000  ef be ad de 0d f0 84 2b  be ba fe ca b1 b0 ad ba  |.......+........|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  41 53 43 49 49 20 54 4f  4f                       |ASCII TOO|
00000029

"best" 答案在很大程度上取决于您访问数据的方式。其他答案已经涵盖了您 API 可能的样子,所以我只关注实施细节。

首先,Windows 似乎不提供原子查找和读取或查找和写入操作,例如 POSIX pread() and pwrite() - which atomically read from or write to a specified offset in a file without modifying the file's offset. (See Are there equivalents to pread on different platforms?) 因此,如果您的目标是多线程环境,它会在不添加锁定的情况下很难使您的 API 可重入和多线程安全。

其次,鉴于您对 随机 访问的要求,基于流的解决方案中内置的缓冲(C++ 流,<cstdio> fopen()fread(), 等) 可能会对性能产生重大的负面影响。例如,如果您使用缓冲 8k 的 <cstdio> 操作,则每次您在 FILE *fseek() 时,您可能会使关联的缓冲区无效。如果您一次只读取少量字节,则每次先查找后读取的缓冲区失效将导致进程读取的字节数显着增加。

我建议使用 fread()/fwrite(),根据您的访问模式可选择使用无缓冲 IO。您可以使用 setbuf():

禁用缓冲
FILE *file = ::fopen(...);
setbuf( file, NULL );

对于您的情况,请参阅 MSDN documentation for setbuf()

使用无缓冲 IO 的一个优点是调用您的 API 的应用程序可能会假定数据在每次调用时都已安全写入磁盘,但在正常缓冲的情况下 <cstdio> 则不然。