【问题标题】:Most efficient way to read lines from text file to std::vector<string>从文本文件读取行到 std::vector<string> 的最有效方法
【发布时间】:2017-05-16 10:37:25
【问题描述】:

将从文本文件中提取的行添加到 std::vector<:string> 中的常用方法,其中向量的每个元素都对应于文件的行,类似于以下示例:

https://stackoverflow.com/a/8365024/7030542

std::string line;
std::vector<std::string> myLines;
while (std::getline(myfile, line))
{
    myLines.push_back(line);
}

也可以

https://stackoverflow.com/a/12506764/7030542

std::vector<std::string> lines;
for (std::string line; std::getline( ifs, line ); /**/ )
    lines.push_back( line );

是否存在一种最有效的方法来做到这一点,比如避免使用辅助字符串?

【问题讨论】:

  • 两种解决方案都很好而且效率很高
  • 这两种解决方案完全等价。

标签: c++ c++11 vector fstream


【解决方案1】:

别想太多:

std::vector<std::string> lines;
std::string line;
while(std::getline( ifs, line ))
    lines.push_back(std::move(line));

请注意,从line 移动的内容处于有效但不确定的状态,因此调用std::getline 很好,因为这将完全替换std::string 的内容(无论它们可能是什么),从而消除任何不确定状态move 留下的。

【讨论】:

  • 也许关于移动语义你是对的,但你没有抓住重点。它不需要另一个字符串来做同样的工作。
  • 另一件事,在您的代码中,当循环第二次运行时,您尝试将新数据添加到处于不确定状态的字符串行中。
  • @Menashe 是的,第二次循环迭代将设置具有不确定内容的有效std::string 对象的内容。由于std::getline替换了std::string的内容,所以这里没有错。
  • 字符串内容不需要在push_back 上复制这一事实是这一举措有所不同的地方。
  • 呵呵,我这些年来一直在使用这种“模式”,而且我从没想过要移出字符串变量。呵呵
【解决方案2】:

@rubenvb 的回答很棒。

作为替代

bool get_line_into_vector( std::istream& is, std::vector<std::string>& v ) {
  std::string tmp;
  if (!std::getline(is, tmp))
     return false;
  v.push_back(std::move(tmp));
  return true;
}

std::vector<std::string> lines;
while(get_line_into_vector( ifs, lines ))
{} // do nothing

这是 rubenvb 的解决方案,将临时移入辅助函数。

我们可以通过以下方式避免小缓冲区优化大小的字符副本:

bool get_line_into_vector( std::istream& is, std::vector<std::string>& v ) {
  v.emplace_back();
  if (std::getline(is, v.back()))
    return true;
  v.pop_back();
  return false;
}

这可能(在极端情况下)导致额外的大规模重新分配,但这是渐近罕见的。

与@pschill 的回答不同,这里的无效状态被隔离在一个辅助函数中,所有的流程控制都围绕着避免这些无效状态泄漏。

好处是

std::vector<std::string> lines;
while(get_line_into_vector( ifs, lines ))
{} // do nothing

是你如何使用它;您使用的这两种实现中的哪一种被隔离在 get_line_into_vector 函数中。这样您就可以在它们之间进行交换并确定哪个更好。

【讨论】:

  • 对于移动语义解决方案,getline 的规范 cpluspluscpprefference 说它不使用缓冲区,但“每个提取的字符都附加到字符串,就好像它的成员 push_back 被调用一样”。
  • 而且 getline 期望一个对字符串的引用,然后将一个处于未定义状态的字符串传递给它,这不是一个好的编程习惯。此外,每个周期“重新创建” tmp 字符串以再次移动它似乎并不比像旧时尚那样在所有周期中保持 tmp 活动更有效。
  • 然后,当您需要在读取数据之前进行内存预留(vector::reserve)时避免使用辅助字符串是一个很好的解决方案(例如,那些将经常获得新元素的向量),否则使用辅助字符串更好。
【解决方案3】:

如果要避免临时变量,可以使用最后一个向量元素作为缓冲区:

std::vector<std::string> lines(1);
while (std::getline(ifs, lines.back())
    lines.emplace_back();
lines.erase(--lines.end());  // remove the buffer element

【讨论】:

  • 这是不推荐的:它更难理解,并且可能不会比移动临时 string 更快(如 rubenvb 的回答)。
  • 牦牛,感谢您的回复。您能否再解释一下它如何导致额外的内存分配以及行何时处于无效状态?更进一步,如何将 emplace 包起来,以制作更高效的代码?
  • @menashe:在最坏的情况下,你的最后一行填满了向量的 storageoplossingen,导致额外的放置的行重新分配整个向量,并使其内存使用加倍。随着最后一个元素的墨水结果再次被删除。当然,这是一个极端情况,它可能会发生。
  • 这可能会发生,但是如果你的向量会频繁获取数据或者它是一个大向量并且还会接收新数据,那么最好保留这个向量。在这种情况下(可能还有其他情况),该解决方案看起来很不错。
  • @MenasheRosemberg 要 ping 某人,请在其姓名前添加一个 @。简单地说明他们的名字不会这样做(除非是为了回应他们的帖子,而不是评论)。 lines 在每一行都处于“无效”状态,除了while 条件之后(但在正文之前)和lines.erase 之后; “无效”是指不在初始状态和最终状态之间的合理中间状态(它最后有一个额外的空字符串)。根据我的经验,这种状态会导致错误。额外的内存分配是因为我们使lines 1 比它必须的大,然后丢弃额外的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-03
  • 2017-08-22
相关资源
最近更新 更多