如何使用istream与字符串

10
我想将一个文件读取到一个字符串中。我正在寻找不同的方法来高效地完成此操作。 使用固定大小的*char缓冲区 我收到了Tony的answer,他创建了一个16 kb的缓冲区,并读取该缓冲区并附加缓冲区,直到没有更多可读取的内容。我理解它的工作原理,而且发现它非常快。但是我不明白的是,在那个答案的评论中说这样做会复制所有东西两次。但在我的理解中,它只发生在内存中,而不是从磁盘上,因此几乎不会被注意到。从缓冲区复制到内存中的字符串是否会有问题? 使用istreambuf_iterator 我收到的other answer使用istreambuf_iterator。代码看起来很美观和简洁,但是速度非常慢。我不知道为什么会这样。为什么这些迭代器如此慢? 使用memcpy()

对于这个问题,我收到了评论,说我应该使用memcpy(),因为它是最快的本地方法。但是我如何在字符串和ifstream对象中使用memcpy()呢?难道ifstream不应该使用自己的read函数吗?为什么使用memcpy()会破坏可移植性?我正在寻找一个既与VS2010兼容又与GCC兼容的解决方案。为什么memcpy()在这些情况下不能工作?

+是否有其他有效的方法?

您推荐我使用什么shell来处理小于10 MB的二进制文件?

(我不想将此问题分成几个部分,因为我更感兴趣的是比较不同的方式,如何将ifstream读取到字符串中)


memcpy()的注释指的是使用内存映射文件进行读取,而不是使用istream进行读取。内存映射文件不具备可移植性,因为它取决于操作系统API。 - Kien Truong
当你在测量性能时,你是在发布模式还是调试模式下进行的?你是否开启了优化?你是否关闭了迭代器检查?默认情况下,Visual Studio具有额外的标准迭代器检查,这可能会影响性能。 - luke
可能是如何为std :: string对象预分配内存的重复问题?这可能是我见过的最精确的重复问题。整个第一句话几乎完全相同(唯一的区别是“我需要…”与“我想要…”)。 - Jerry Coffin
2个回答

10

这只发生在内存中,而不是从磁盘中读取,所以几乎不会被注意到。

确实如此。不过,没有这种情况的解决方案可能会更快。

为什么这些迭代器如此缓慢?

代码之所以缓慢并不是因为迭代器的原因,而是因为字符串不知道要分配多少内存: istreambuf_iterator 只能被遍历一次,所以字符串本质上被迫执行重复的连接操作,并伴随着内存重新分配,这非常缓慢。

来自另一个答案,我最喜欢的一行代码可以直接从底层缓冲区进行流式传输:

string str(static_cast<stringstream const&>(stringstream() << in.rdbuf()).str());

在最近的平台上,这确实会预分配缓冲区。不过仍然会导致多余的拷贝(从 stringstream 到最终字符串)。


1
我只是在测试不同的解决方案,而你的比所有基于迭代器的解决方案快大约8倍。非常好的一个。 - Björn Pollex

4

最通用的方式可能是使用istreambuf_iterator响应:

std::string s( (std::istreambuf_iterator<char>( source )),
               (std::istreambuf_iterator<char>()) );

尽管确切的表现很大程度上取决于实现,但这几乎不可能是最快的解决方案。

一个有趣的替代方案是:

std::istringstream tmp;
tmp << source.rdbuf();
std::string s( tmp.str() );

这可能非常快,如果实现在使用operator<<和在istringstream中增长字符串方面做得很好。然而,一些早期的实现(以及可能是一些更近期的实现)在这方面做得非常糟糕。
通常情况下,使用std::string的性能取决于实现在增长字符串方面的效率;实现无法确定初始大小。您可能希望将第一个算法与使用std::vector<char>相同的代码进行比较,而不是使用std::string,或者如果您可以很好地估计最大大小,则使用reserve或类似方法:
std::string s( expectedSize, '\0' );
std::copy( std::istreambuf_iterator<char>( source ),
           std::istreambuf_iterator<char>(),
           s.begin() );

memcpy不能从文件中读取数据,并且在使用良好的编译器时,使用相同数据类型的std::copy速度不会比memcpy快。

我倾向于使用第二种解决方案,即在rdbuf()上使用<<,但这部分是出于历史原因; 我习惯于在标准库中添加STL之前这样做(使用istrstream)。 说到这一点,您可能想尝试使用istrstream和预分配的缓冲区(假设您可以找到适当的缓冲区大小)。


如果源流是可寻址的,您可以通过执行 source.seekg(0,std::ios_base::end); std::streampos pos=source.tellg(); source.seekg(0,std::ios_base::beg); 来获取其大小。在此之后,如果 source 仍然正常并且 pos!=-1,则 pos 将是文件的大小等。我过去曾经使用过这个方法。 - sbi
@sbi 这在大多数Unix实现中都有效,但在Windows上不行,至少如果文件以文本模式打开。而且甚至不能保证编译通过。 - James Kanze
@James:你能详细说明一下吗?我知道我在一个跨平台应用程序中使用了它,我认为它可以在Win32、OSX、BSD、Linux、Solaris和其他一些操作系统上运行。 - sbi
@James:谢谢您。std::streampos无法转换或其值没有传达任何含义确实可能会是一个停止符。我不知道这一点。至于什么被认为是大小:tellg()报告的值是否以与流式处理相同的二进制/文本方式呈现?(但是,即使它不是,通常这将占文件大小的10%左右。因此,它可能会导致一个额外的分配而不是任意数量。) - sbi
在 Windows 上,tellg() 报告的值对于任何给定文件都是相同的,无论是在文本模式还是二进制模式下打开。但是,您可以读取的字符数不会相同。但是,为了确定缓冲区大小,它可能已经足够了,因为您可以读取的字符数始终小于或等于 tellg() 的结果。通常,对于文本模式文件,tellg() 不会比您可以读取的内容多太多,但可能会有明显的差异。 - James Kanze
显示剩余2条评论

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