流成向量

Streaming into vector?

本文关键字:向量      更新时间:2023-10-16

我偶然发现了这段代码,用于将文件的内容插入到vector中。似乎是一个有用的东西来学习如何做:

#include <iostream>
#include <fstream>
#include <vector>
int main() {
   typedef std::vector<char> fileContainer;
   std::ifstream testFile("testfile.txt");
   fileContainer container;
   container.assign(
      (std::istreambuf_iterator<char>(testFile)),
      std::istreambuf_iterator<char>());
    return 0;
}

它工作,但我想问的是,这是最好的方式做这样的事情吗?也就是说,获取任意文件类型的内容并将其插入到适当的STL容器中。有没有比上面更有效的方法?据我所知,它创建了ifstream的testFile实例,并用testFile .txt的内容填充它,然后该副本再次通过分配复制到容器中。看起来抄袭很多?

至于速度/效率,我不确定如何估计文件大小并使用保留函数,如果我使用保留,它似乎会减慢这段代码的速度。目前,换出vector并使用deque似乎更有效一些。

我不确定是否有最好的方法,但是使用两个迭代器构造函数应该更习惯:

FileContainer container( (std::istreambuf_iterator<char>( testFile )),
                         (std::istreambuf_iterator<char>()) );

(我注意到您的assign中有额外的括号。他们在这里不是必需的,但在使用构造函数时是必需的。)

考虑到性能,预分配会更有效数据,类似于:

FileContainer container( actualSizeOfFile );
std::copy( std::istreambuf_iterator<char>( testFile ),
           std::istreambuf_iterator<char>(),
           container.begin() );

这有点危险;如果你的估计太小,你就会遇到未定义的行为。为了避免这种情况,您还可以这样做:

FileContainer container;
container.reserve( estimatedSizeOfFile );
container.insert( container.begin(),
                  std::istreambuf_iterator<char>( testFile ),
                  std::istreambuf_iterator<char>() );

哪一个更快取决于实现;最后一个我测量的时间(用g++),第一个稍微快一点,但是如果你实际上从文件中读取,差异可能是不可测量的。

这两种方法的问题是,尽管有其他的答案,有找到文件大小的便携方法除了实际吗读取文件。对于某些系统(fstat)存在不可移植的方法在Unix下),但在其他系统(如Windows)上,没有表示找到char的确切数量,你可以从一个文本文件中读取。当然,也不能保证tellg()的结果会甚至转换成整型,如果转换成整型,它们也不会做一个神奇的饼干,没有数字意义。

话虽如此,在实践中,tellg()的使用建议其他海报通常是"可移植的"(Windows和大多数Unix)最少),结果往往会"足够接近";它们通常是在Windows下有点高(因为结果将计算)回车符(不会被读取),但在很多情况下,这不是什么大问题。最后,是由你来决定做什么你的要求是关于便携性和精度大小。

它创建一个ifstream的testFile实例,并用testFile .txt

的内容填充它。

不,它打开testfile.txt并调用句柄testFile。从磁盘到内存有一个拷贝。(除了I/O通常是通过内核空间的另一份拷贝来完成,但您不会以可移植的方式避免这种情况。)

至于速度/效率,我不知道如何估计文件大小和使用保留函数

如果文件是普通文件:

std::ifstream testFile("testfile.txt");
testFile.seekg(0, std::ios::end);
std::ios::streampos size = testFile.tellg();
testFile.seekg(0, std::ios::beg);
std::vector<char> container;
container.reserve(size);

然后像以前一样填充container。或者将其构造为std::vector<char> container(size)并用

填充
testFile.read(&container.front, size);

哪一个更快应该通过分析来确定。

std::ifstream不包含文件的内容,内容按需读取。这涉及到某种缓冲,因此文件将以k字节的块读取。由于流迭代器是InputIterators,因此首先在vector上调用reserve应该更有效;但前提是您已经有了这些信息,或者可以猜测出一个很好的近似值,否则您必须遍历文件内容两次。

人们更希望从文件中读取到字符串而不是vector。如果你能用这个,你可能想看看我对上一个问题的回答。

对第四个测试进行一个小的编辑,会得到这样的结果:

std::vector<char> s4;
file.seekg(0, std::ios::end);
s4.resize(file.tellg());
file.seekg(0, std::ios::beg);
file.read(&s4[0], s4.size());

我的猜测是,这应该使性能基本上与使用字符串的代码没有区别。根据您的编译器/标准库,这可能比您当前的代码要快得多(同样,请参阅计时结果,了解您可能看到的差异)。

还要注意,这提供了一点额外的能力来检测和诊断错误。例如,您可以通过比较s4.size()file.gcount()(和/或检查file.eof())来检查是否成功读取了整个文件。这也使得通过限制您读取的数量来防止问题变得更容易一些,以防有人想看看当他们试图使用您的程序读取一个6tb的文件时会发生什么。

如果你想让它更有效率,肯定有更好的方法。您可以检查文件大小,预先分配向量和直接读取到向量的内存。一个简单的例子:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <iostream>
using namespace std;
int main ()
{
    int fd = open ("test.data", O_RDONLY);
    if (fd == -1)
    {
        perror ("open");
        return EXIT_FAILURE;
    }
    struct stat info;
    int res = fstat (fd, &info);
    if (res != 0)
    {
        perror ("fstat");
        return EXIT_FAILURE;
    }
    std::vector<char> data;
    if (info.st_size > 0)
    {
        data.resize (info.st_size);
        ssize_t x = read (fd, &data[0], data.size ());
        if (x != info.st_size)
        {
            perror ("read");
            return EXIT_FAILURE;
        }
        cout << "Data (" << info.st_size << "):n";
        cout.write (&data[0], data.size ());
    }
}

对于某些任务还有其他更有效的方法。例如,要复制文件而不向用户空间传输数据,您可以使用sendfile

它确实有效,而且很方便,但在许多情况下,这是一个坏主意。

例如,

用户编辑文件中的错误处理。如果用户手工编辑了一个数据文件,或者它是从具有松散字段定义的电子表格甚至数据库中导入的,那么这种填充向量的方法将导致一个没有细节的简单错误。

为了处理文件并报告发生错误的,您需要逐行读取它,并尝试在每行上转换为数字。然后可以报告转换失败的行号和文本。这非常有用。如果没有这个特性,用户就会怀疑是哪一行引起了问题,而不是能够立即修复它。