如何将二进制文件读入无符号字符的向量


76

最近我一直在问编写一个函数读取二进制文件到std::vector<BYTE>哪里BYTEunsigned char。我很快就想到了这样的东西:

#include <fstream>
#include <vector>
typedef unsigned char BYTE;

std::vector<BYTE> readFile(const char* filename)
{
    // open the file:
    std::streampos fileSize;
    std::ifstream file(filename, std::ios::binary);

    // get its size:
    file.seekg(0, std::ios::end);
    fileSize = file.tellg();
    file.seekg(0, std::ios::beg);

    // read the data:
    std::vector<BYTE> fileData(fileSize);
    file.read((char*) &fileData[0], fileSize);
    return fileData;
}

这似乎不必要地复杂,并且char*在调用时被迫使用的显式强制转换file.read不会使我对此有所改善。


另一种选择是使用std::istreambuf_iterator

std::vector<BYTE> readFile(const char* filename)
{
    // open the file:
    std::ifstream file(filename, std::ios::binary);

    // read the data:
    return std::vector<BYTE>((std::istreambuf_iterator<char>(file)),
                              std::istreambuf_iterator<char>());
}

这非常简单且简短,但是std::istreambuf_iterator<char>当我读入时,我仍然必须使用甚至std::vector<unsigned char>


似乎非常简单明了的最后一个选项是use std::basic_ifstream<BYTE>,它明确表示“我想要输入文件流,并且想要使用它读取BYTEs”

std::vector<BYTE> readFile(const char* filename)
{
    // open the file:
    std::basic_ifstream<BYTE> file(filename, std::ios::binary);

    // read the data:
    return std::vector<BYTE>((std::istreambuf_iterator<BYTE>(file)),
                              std::istreambuf_iterator<BYTE>());
}

但我不确定basic_ifstream在这种情况下是否合适的选择。

将二进制文件读入的最佳方法是vector什么?我还想知道“幕后”正在发生什么,以及我可能遇到的可能的问题(除了无法正确打开流,可以通过简单的is_open检查来避免此问题)。

有什么充分的理由为什么人们更喜欢在std::istreambuf_iterator这里使用?
(我看到的唯一优点是简单)


1
@ R.MartinhoFernandes:我的意思是说第三种选择似乎并不比第二种更好。
LihO 2013年

有人(至少在2011年)对它进行了测量,以便至少加载到字符串中。insanecoding.blogspot.hk/2011/11/how-to-read-in-file-in-c.html
jiggunjer

查找尺寸的更安全方法:使用特殊ignore() 计数:file.ignore(std::numeric_limits<std::streamsize>::max());,并std::streamsize使用auto size =file.gcount();
Brett Hale'8

Answers:


45

在测试性能时,我将包括以下测试用例:

std::vector<BYTE> readFile(const char* filename)
{
    // open the file:
    std::ifstream file(filename, std::ios::binary);

    // Stop eating new lines in binary mode!!!
    file.unsetf(std::ios::skipws);

    // get its size:
    std::streampos fileSize;

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

    // reserve capacity
    std::vector<BYTE> vec;
    vec.reserve(fileSize);

    // read the data:
    vec.insert(vec.begin(),
               std::istream_iterator<BYTE>(file),
               std::istream_iterator<BYTE>());

    return vec;
}

我的想法是方法1的构造函数触及中的元素vector,然后read再次触及每个元素。

方法2和方法3看起来最有前途,但可能会遭受一个或多个问题resize。因此reserve阅读或插入之前的原因。

我也会用std::copy

...
std::vector<byte> vec;
vec.reserve(fileSize);

std::copy(std::istream_iterator<BYTE>(file),
          std::istream_iterator<BYTE>(),
          std::back_inserter(vec));

最后,我认为最好的解决方案将避免operator >>istream_iterator(和所有的开销和善良的operator >>试图解释二进制数据)。但是我不知道该使用什么使您直接将数据复制到向量中。

最后,我对二进制数据的测试表明ios::binary不兑现。因此,之所以noskipws<iomanip>


有没有一种方法可以将特定大小的数据读取到数组中,而不是像此处描述的那样读取整个文件?
超级英雄

1
我以为您只需要file.unsetf(std::ios::skipws);使用运算符就可以了>>
jiggunjer 2015年

我需要的file.unsetf(std::ios::skipws);,即使使用std::copy拷贝到一个vector,否则我会丢失数据。这是Boost 1.53.0。
凤凰城

1
@jiggunjer在内部std::istream_iterator使用>>运算符从流中提取数据。
tomi.lee.jones

尝试了8个以上的代码段,但这些代码均无效,非常感谢!+1
MikeTheCoder

17
std::ifstream stream("mona-lisa.raw", std::ios::in | std::ios::binary);
std::vector<uint8_t> contents((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());

for(auto i: contents) {
    int value = i;
    std::cout << "data: " << value << std::endl;
}

std::cout << "file size: " << contents.size() << std::endl;

7

由于要将整个文件加载到内存中,因此最佳的版本是将文件映射到内存中。这是因为内核无论如何都将文件加载到内核页面缓存中,并且通过映射文件,您只需将缓存中的那些页面公开到进程中即可。也称为零复制。

当您使用std::vector<>它时,仅从内核页面缓存中复制数据,而std::vector<>您只想读取文件时就不需要将数据复制到其中。

另外,将两个输入迭代器传递给std::vector<>它时,由于不知道文件大小,因此在读取时会增加其缓冲区。std::vector<>首先调整为文件大小时,它会不必要地将其内容清零,因为无论如何它将被文件数据覆盖。两种方法在时间和空间上都不理想。


是的,如果内容不必在矢量中,那绝对是最好的方法。
Mats Petersson

而不是resizereserve犯规初始化。
jiggunjer 2015年

这意味着您可以将迭代器传递给保留向量,以避免多余的调整大小。参考您的最后一段。
jiggunjer 2015年

1
@jiggunjer好吧,这将不起作用,因为您必须先调整向量大小才能访问保留的容量。
Maxim Egorushkin

1
对于不参考标准阅读的人来说,这还不清楚。它没有说明如何映射到内存-我假设 streambufbasic这样做?另外,该术语还假设使用的是Linux / UNIX操作系统,这似乎并不适用于所有平台-C ++可定位的所有操作系统是否都存在相同的概念和最佳实践?
underscore_d

3

我以为第一种方法,使用大小和使用stream::read()将是最有效的。char *强制转换为的“成本”很可能为零-这种强制转换只是告诉编译器“嘿,我知道您认为这是一种不同的类型,但我确实希望在此使用此类型...”,并且不添加任何额外的指令-如果您希望确认这一点,请尝试将文件读入char数组,然后比较实际的汇编代码。除了进行一些额外的工作以找出向量内部缓冲区的地址外,应该没有任何区别。

与往常一样,唯一确定哪种情况最有效的方法就是进行测量。“在互联网上问”不是证据。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.