将整个 ASCII 文件读入 C++ std::string [重复]

Posted

技术标签:

【中文标题】将整个 ASCII 文件读入 C++ std::string [重复]【英文标题】:Read whole ASCII file into C++ std::string [duplicate] 【发布时间】:2011-02-05 19:37:54 【问题描述】:

我需要将整个文件读入内存并将其放入 C++ std::string

如果我把它读成char[],答案会很简单:

std::ifstream t;
int length;
t.open("file.txt");      // open input file
t.seekg(0, std::ios::end);    // go to the end
length = t.tellg();           // report location (this is the length)
t.seekg(0, std::ios::beg);    // go back to the beginning
buffer = new char[length];    // allocate memory for a buffer of appropriate dimension
t.read(buffer, length);       // read the whole file into the buffer
t.close();                    // close file handle

// ... Do stuff with buffer here ...

现在,我想做完全相同的事情,但使用std::string 而不是char[]。我想避免循环,即我不想想要:

std::ifstream t;
t.open("file.txt");
std::string buffer;
std::string line;
while(t)
std::getline(t, line);
// ... Append line to buffer and go on

t.close()

有什么想法吗?

【问题讨论】:

总会涉及到一个循环,但它可以作为标准库的一部分隐含。这可以接受吗?你为什么要避免循环? 我相信发帖者知道读取字节涉及循环。他只是想要一个简单的、perl 风格的 gulp 等价物。这涉及编写少量代码。 如果 std::string 不为其字符串数据使用连续缓冲区(这是允许的),此代码有问题:***.com/a/1043318/1602642 @ChrisDesjardins:(1) 您的链接已过时(C++11 使其连续)并且 (2) 即使不是,std::getline(istream&, std::string&) 仍然会做正确的事情。跨度> 查看此代码的任何人的旁注:作为读取 char[] 示例的代码不会以空值终止数组(读取不会自动执行此操作),这可能不是你期待。 【参考方案1】:

有几种可能性。我喜欢使用字符串流作为中间人:

std::ifstream t("file.txt");
std::stringstream buffer;
buffer << t.rdbuf();

现在“file.txt”的内容以buffer.str()的字符串形式提供。

另一种可能性(虽然我当然也不喜欢它)更像你原来的:

std::ifstream t("file.txt");
t.seekg(0, std::ios::end);
size_t size = t.tellg();
std::string buffer(size, ' ');
t.seekg(0);
t.read(&buffer[0], size); 

正式地,这在 C++98 或 03 标准下不需要工作(字符串不需要连续存储数据),但实际上它适用于所有已知的实现,C++11 及更高版本可以需要连续存储,因此可以保证与它们​​一起使用。

至于为什么我也不喜欢后者:首先,因为它更长且更难阅读。其次,因为它要求您使用您不关心的数据初始化字符串的内容,然后立即覆盖该数据(是的,与读取相比,初始化时间通常微不足道,所以这可能无关紧要,但对我来说仍然感觉有点不对)。第三,在文本文件中,文件中的位置 X 并不一定意味着您已经阅读了 X 个字符才能到达该位置——它不需要考虑诸如行尾翻译之类的事情。在进行此类翻译的真实系统(例如,Windows)上,翻译后的形式比文件中的短(即,文件中的“\r\n”在翻译后的字符串中变成“\n”)所以你所做的一切保留了一点你从不使用的额外空间。再说一次,并没有真正引起大问题,但总感觉有点不对劲。

【讨论】:

三线的工作就像一个魅力! 这应该被标记为答案。 对某些人来说重要的提示,至少在我的实现中,对于 50KB 以下的文件,三行代码至少与 C fopen 替代方案一样好。过去,它似乎很快失去了性能。在这种情况下,只需使用第二种解决方案。 确保#include 大多数情况下,测试文件是否已打开(其他操作将简单地失败)。作为一项规则,您应该避免当场打印出错误消息,除非您确定这与程序的其余部分相符——如果您必须做某事,抛出异常通常是可取的。你也几乎不应该明确地关闭一个文件——析构函数会自动完成。【参考方案2】:

更新: 事实证明,这种方法虽然很好地遵循了 STL 习语,但实际上效率低得惊人!不要对大文件执行此操作。 (见:http://insanecoding.blogspot.com/2011/11/how-to-read-in-file-in-c.html

你可以从文件中创建一个streambuf迭代器并用它初始化字符串:

#include <string>
#include <fstream>
#include <streambuf>

std::ifstream t("file.txt");
std::string str((std::istreambuf_iterator<char>(t)),
                 std::istreambuf_iterator<char>());

不确定您从哪里获得 t.open("file.txt", "r") 语法。据我所知,这不是std::ifstream 拥有的方法。看来您已将其与 C 的 fopen 混淆了。

编辑:还要注意字符串构造函数的第一个参数周围的额外括号。 这些是必不可少的。它们防止了被称为“most vexing parse”的问题,在这种情况下,它实际上不会像通常那样给你一个编译错误,而是会给你有趣的(阅读:错误)结果。

按照 KeithB 在 cmets 中的观点,这是一种预先分配所有内存的方法(而不是依赖于字符串类的自动重新分配):

#include <string>
#include <fstream>
#include <streambuf>

std::ifstream t("file.txt");
std::string str;

t.seekg(0, std::ios::end);   
str.reserve(t.tellg());
t.seekg(0, std::ios::beg);

str.assign((std::istreambuf_iterator<char>(t)),
            std::istreambuf_iterator<char>());

【讨论】:

open 绝对是ifstream的一个方法,但是第二个参数是错误的。 cplusplus.com/reference/iostream/ifstream/open @KeithB 如果效率很重要,您可以找到与char* 示例中相同的文件长度,然后调用std::string::reserve 预分配必要的空间。 不知道人们为什么要投票,这是一个简单的问题,假设我有一个 1MB 的文件,“end”将传递给 std::string 构造函数或分配方法多少次被调用?人们认为这类解决方案很优雅,但实际上它们是“如何不做”的绝佳示例。 基准测试:Tyler 的两种解决方案在 267 MB 文件上都需要大约 21 秒。 Jerry 的第一个需要 1.2 秒,第二个需要 0.5 (+/- 0.1),所以很明显 Tyler 的代码效率低下。 insanecoding 博客文章是针对一个稍微不同的问题的基准解决方案:它将文件作为二进制而不是文本读取,因此没有行尾的翻译。作为副作用,读取为二进制文件使 ftell 成为获取文件长度的可靠方法(假设 long 可以表示文件长度,但不能保证)。对于确定长度, ftell 在文本流上是不可靠的。如果您正在从磁带读取文件(例如,备份),那么额外的查找可能会浪费时间。许多博客文章实现不使用 RAII,因此如果出现错误可能会泄漏。【参考方案3】:

我认为最好的方法是使用字符串流。简单快捷!!!

#include <fstream>
#include <iostream>
#include <sstream> //std::stringstream
int main() 
    std::ifstream inFile;
    inFile.open("inFileName"); //open the input file

    std::stringstream strStream;
    strStream << inFile.rdbuf(); //read the file
    std::string str = strStream.str(); //str holds the content of the file

    std::cout << str << "\n"; //you can do anything with the string!!!

【讨论】:

简单快捷对吧! insanecoding.blogspot.com/2011/11/how-to-read-in-file-in-c.html 之后记得关闭流... @YngveSneenLindal 或者让析构函数自动完成——利用 C++! @YngveSneenLindal 您确定之后需要关闭流吗?显然,一旦 fstream 被销毁(超出范围),应该释放 fstream 的内存分配?虽然使用.close() 进行错误检查会很好? 为什么在几年前 Jerry Coffin 已经在答案中发布这个?【参考方案4】:

你可能在任何书籍或网站上都找不到这个,但我发现它运作良好:

#include <fstream>
// ...
std::string file_content;
std::getline(std::ifstream("filename.txt"), file_content, '\0');

【讨论】:

eof 转换为(char) 有点狡猾,暗示了某种虚幻的相关性和普遍性。对于eof() 和签名char 的一些可能值,它将给出实现定义的结果。直接使用例如char(0) / '\0' 会更强大,更诚实地表明正在发生的事情。 @TonyD。关于将 eof() 转换为 char 的要点。我想对于老式的 ascii 字符集,传递任何负值(msb 设置为 1)都可以。但是传递 \0 (或负值)不适用于宽或多字节输入文件。 只有在文件中没有“eof”(例如 0x00、0xff、...)字符的情况下,这才有效。如果有,您将只读取文件的一部分。 @OlafDietsche ASCII 文件中不应该有 0x00(或者我不会称它为 ASCII 文件)。 0x00 在我看来是一个不错的选择,可以强制 getline() 读取整个文件。而且,我必须承认,尽管获得更高票数的解决方案看起来更令人印象深刻和复杂,但这段代码很短很容易阅读。 @Scheff 重新审视这个答案后,我不知道我是如何得出这个结论和评论的。也许我想,(char) ifs.eof() 有一定的意义。 eof()此时返回false,调用等价于std::getline(ifs, s, 0);。所以它会一直读取到第一个 0 字节,或者文件末尾,如果没有 0 字节的话。【参考方案5】:

尝试以下两种方法之一:

string get_file_string()
    std::ifstream ifs("path_to_file");
    return string((std::istreambuf_iterator<char>(ifs)),
                  (std::istreambuf_iterator<char>()));


string get_file_string2()
    ifstream inFile;
    inFile.open("path_to_file");//open the input file

    stringstream strStream;
    strStream << inFile.rdbuf();//read the file
    return strStream.str();//str holds the content of the file

【讨论】:

【参考方案6】:

我想出了另一种适用于大多数 istream 的方法,包括 std::cin!

std::string readFile()

    stringstream str;
    ifstream stream("Hello_World.txt");
    if(stream.is_open())
    
        while(stream.peek() != EOF)
        
            str << (char) stream.get();
        
        stream.close();
        return str.str();
    

【讨论】:

【参考方案7】:

如果你碰巧使用glibmm,你可以试试Glib::file_get_contents。

#include <iostream>
#include <glibmm.h>

int main() 
    auto filename = "my-file.txt";
    try 
        std::string contents = Glib::file_get_contents(filename);
        std::cout << "File data:\n" << contents << std::endl;
    catch (const Glib::FileError& e) 
        std::cout << "Oops, an error occurred:\n" << e.what() << std::endl;
    

    return 0;

【讨论】:

恕我直言:虽然这可行,但如果有一个简单的 CPP 标准解决方案它。【参考方案8】:

我可以这样做:

void readfile(const std::string &filepath,std::string &buffer)
    std::ifstream fin(filepath.c_str());
    getline(fin, buffer, char(-1));
    fin.close();

如果这令人不悦,请告诉我原因

【讨论】:

char(-1) 可能不是表示 EOF 的可移植方式。此外,我认为 getline() 实现不需要支持“无效”EOF 伪字符作为分隔符。 @reddish 确实不是,在现代 C++ 中最好使用std::char_traits&lt;char&gt;::eof()。如果有人仍在使用古老的编译器...&lt;cstdio&gt; 包含 EOF 宏。【参考方案9】:

如果没有显式或隐式循环,不先读入一个 char 数组(或其他容器),再读入十个构造字符串,我认为您无法做到这一点。如果您不需要字符串的其他功能,可以使用 vector&lt;char&gt; 来完成,就像您当前使用 char * 一样。

【讨论】:

-1 不正确...见上文 好吧,说句公道话,以上所有答案都以某种方式包含一个循环,无论是作为样板还是幕后...

以上是关于将整个 ASCII 文件读入 C++ std::string [重复]的主要内容,如果未能解决你的问题,请参考以下文章

Windows C++ API:如何将整个二进制文件读入缓冲区?

.txt 文件中的 Ascii 控制字符问题、XOR 加密、C++

C++ 图像处理 - 将图像文件读入二维数组

将混合数据文件读入 C++ 字符串

将文本文件逐行读入并行数组C++

将整个二进制文件读入 Python