C++:将读取的二进制文件存储到缓冲区

9

我正在尝试读取一个二进制文件并将其存储在缓冲区中。问题在于,该二进制文件中有多个以null终止的字符,但它们不是在末尾,而是出现在其他二进制文本之前,因此如果我在'\0'后面存储文本,它会在缓冲区中将其删除。

例如:

char * a = "this is a\0 test";
cout << a;

这将只输出:this is a 这是我的真实代码:
该函数读取一个字符。
bool CStream::Read  (int * _OutChar)
{
    if (!bInitialized)
        return false;

    int iReturn = 0;

     *_OutChar = fgetc (pFile);

    if (*_OutChar == EOF)
        return false;

    return true;
}

这是我如何使用它:

    char * SendData = new char[4096 + 1];

    for (i = 0; i < 4096; i++)
    {
        if (Stream.Read (&iChar))
            SendData[i] = iChar;
        else
            break;
    }

你的问题是关于如何处理读取到的数据吗?因为假设打开模式为std::binary,读取似乎没问题,缓冲区的使用也正确。 - Christophe
@Christophe 我正在使用fopen打开文件,并将模式参数设置为"ab + ",我认为这是二进制的。接下来,我使用ssl_write将缓冲区发送到套接字,在那里我将缓冲区写回文件,但是这失败了,因为接收到的缓冲区不完整,它只写入缓冲区直到'\0'。 - schacker22
5个回答

16

我想提一下,读取二进制文件到缓冲区有一种标准的方法。

使用 <cstdio>

char buffer[BUFFERSIZE];

FILE * filp = fopen("filename.bin", "rb"); 
int bytes_read = fread(buffer, sizeof(char), BUFFERSIZE, filp);

使用 <fstream>

std::ifstream fin("filename.bin", ios::in | ios::binary );
fin.read(buffer, BUFFERSIZE);

当然,之后您对缓冲区的处理完全取决于您自己。

编辑:使用<cstdio>的完整示例

#include <cstdio>

const int BUFFERSIZE = 4096;    

int main() {
    const char * fname = "filename.bin";
    FILE* filp = fopen(fname, "rb" );
    if (!filp) { printf("Error: could not open file %s\n", fname); return -1; }

    char * buffer = new char[BUFFERSIZE];
    while ( (int bytes = fread(buffer, sizeof(char), BUFFERSIZE, filp)) > 0 ) {
        // Do something with the bytes, first elements of buffer.
        // For example, reversing the data and forget about it afterwards!
        for (char *beg = buffer, *end=buffer + bytes; beg < end; beg++, end-- ) {
           swap(*beg, *end);
        }
    }

    // Done and close.
    fclose(filp);

    return 0;
}

如果我想读取一个超过1GB的文件,我不能使用fread,因为缓冲区大小会太大。这是因为我正在使用fgets逐个字符地读取。 - schacker22
2
@schacker22,使用fread读取较小的缓冲区仍然可能比fget获得更好的性能。fread没有规定你必须一次性读取整个文件。 - Stian Svedenborg
我已经尝试过这个了,请查看这个链接:http://stackoverflow.com/questions/24712427/c-read-files-in-4096b-steps - schacker22
@schacker22,我看了你的代码,第一眼看起来好像没什么问题,除非fseek返回一个错误。请注意,只要你没有在文件中跳来跳去,调用fseek是不需要的。 - Stian Svedenborg
1
文件中的当前位置存储在FILE对象中,下一次调用fread将从上次读取结束的地方继续。这与fget的方式相同。唯一的区别是fread一次读取更多的数据,因此产生的开销较小。 - Stian Svedenborg
显示剩余3条评论

6
static std::vector<unsigned char> read_binary_file (const std::string filename)
{
    // binary mode is only for switching off newline translation
    std::ifstream file(filename, std::ios::binary);
    file.unsetf(std::ios::skipws);

    std::streampos file_size;
    file.seekg(0, std::ios::end);
    file_size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<unsigned char> vec;
    vec.reserve(file_size);
    vec.insert(vec.begin(),
               std::istream_iterator<unsigned char>(file),
               std::istream_iterator<unsigned char>());
    return (vec);
}

然后

auto vec = read_binary_file(filename);
auto src = (char*) new char[vec.size()];
std::copy(vec.begin(), vec.end(), src);

1
对于任何想要使用此代码的人:read_binary_file函数有一行std::vector<unsigned char> vec(file_size)。它应该只是std::vector<unsigned char> vec; 否则,该函数将返回一个向量,其中包含文件内容开头的x个字节,后跟x个虚拟字节。该向量具有2x个字节!如果您想在将文件读入向量时优化内存分配,请在创建向量后立即使用vec.reserve(file_size)。 - kalyanswaroop
FYI - 以上评论的代码已更新。 - Goblinhack

2
问题明显出在你的缓冲区写入上,因为你每次只读取一个字节。如果你知道缓冲区中数据的长度,你可以强制cout继续输出。
char *bf = "Hello\0 world"; 
cout << bf << endl;
cout << string(bf, 12) << endl;

这应该会产生以下输出:
Hello
Hello  world

然而,这只是一种变通方法,因为cout被设计用于输出可打印数据。请注意,像'\0'这样的非可打印字符的输出取决于系统。

替代方案:

但是,如果您要操作二进制数据,则应定义特定的数据结构和打印方式。以下是一些提示和一般原则的快速草图:

struct Mybuff {   // special strtucture to manage buffers of binary data
    static const int maxsz = 512; 
    int size;
    char buffer[maxsz]; 
    void set(char *src, int sz)  // binary copy of data of a given length
    { size = sz; memcpy(buffer, src, max(sz, maxsz)); }
} ; 

那么您可以重载输出运算符函数:
ostream& operator<< (ostream& os, Mybuff &b)
{
    for (int i = 0; i < b.size; i++) 
        os.put(isprint(b.buffer[i]) ? b.buffer[i]:'*');  // non printables replaced with *
    return os;
}

您可以像这样使用它:
char *bf = "Hello\0 world"; 
Mybuff my; 
my.set(bf, 13);   // physical copy of memory
cout << my << endl;   // special output 

1
我相信你的问题不在于读取数据,而是在于如何打印它。
char * a = "this is a\0 test";
cout << a;

这个示例展示了打印C字符串。由于C字符串是以'\0'结尾的char序列,打印函数会在遇到第一个空字符时停止。这是因为你需要知道字符串的结束位置,可以通过使用特殊的终止字符(如此处的'\0')或者知道其长度来实现。
因此,要打印整个数据,必须知道它的长度,并使用类似读取它的循环。

0

你使用的是Windows系统吗?如果是,你需要执行_setmode(_fileno(stdout), _O_BINARY);

同时需要包含<fcntl.h><io.h>


这是针对fopen函数还是socket的? - schacker22

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接