如何在 C++ 中将整个文件读入 std::string?
Posted
技术标签:
【中文标题】如何在 C++ 中将整个文件读入 std::string?【英文标题】:How do I read an entire file into a std::string in C++? 【发布时间】:2010-09-12 01:50:02 【问题描述】:如何将文件读入std::string
,即一次读取整个文件?
文本或二进制模式应由调用者指定。该解决方案应符合标准、可移植且高效。它不应该不必要地复制字符串的数据,并且应该避免在读取字符串时重新分配内存。
一种方法是统计文件大小,将std::string
和fread()
调整为std::string
的const_cast<char*>()
'ed data()
。这要求std::string
的数据是连续的,这不是标准所要求的,但似乎所有已知实现都是如此。更糟糕的是,如果以文本模式读取文件,std::string
的大小可能不等于文件的大小。
可以使用std::ifstream
的rdbuf()
到std::ostringstream
并从那里到std::string
构建完全正确、符合标准和可移植的解决方案。但是,这可能会复制字符串数据和/或不必要地重新分配内存。
void slurp(std::string& data, bool is_binary)
【问题讨论】:
请注意,您仍有一些未指定的内容。例如,文件的字符编码是什么?您会尝试自动检测(仅在少数特定情况下有效)吗?你会尊重例如XML 标头告诉您文件的编码?也没有“文本模式”或“二进制模式”之类的东西——你在想 FTP 吗? 文本和二进制模式是 MSDOS 和 Windows 特定的 hack,它们试图绕过换行符在 Windows (CR/LF) 中由两个字符表示的事实。在文本模式下,它们被视为一个字符('\n')。 虽然不是(完全)完全重复,但这与:how to pre-allocate memory for a std::string object? 密切相关(与上面 Konrad 的声明相反,它包含执行此操作的代码,将文件直接读入目标,没有做一个额外的副本)。 “标准不需要连续” - 是的,以迂回的方式。一旦你在字符串上使用 op[],它必须被合并到一个连续的可写缓冲区中,因此如果你首先 .resize() 足够大,就可以保证写入 &str[0] 是安全的。而在 C++11 中,字符串总是连续的。 相关链接:How to read a file in C++? -- 基准测试并讨论各种方法。是的,rdbuf
(接受的答案中的那个)不是最快的,read
是。
【参考方案1】:
一种方法是将流缓冲区刷新为单独的内存流,然后将其转换为std::string
(省略错误处理):
std::string slurp(std::ifstream& in)
std::ostringstream sstr;
sstr << in.rdbuf();
return sstr.str();
这非常简洁。但是,正如问题中所述,这会执行冗余副本,不幸的是,基本上没有办法删除此副本。
不幸的是,唯一避免冗余副本的真正解决方案是手动循环读取。由于 C++ 现在已经保证了连续的字符串,因此可以编写以下内容(≥C++17,包括错误处理):
auto read_file(std::string_view path) -> std::string
constexpr auto read_size = std::size_t(4096);
auto stream = std::ifstream(path.data());
stream.exceptions(std::ios_base::badbit);
auto out = std::string();
auto buf = std::string(read_size, '\0');
while (stream.read(& buf[0], read_size))
out.append(buf, 0, stream.gcount());
out.append(buf, 0, stream.gcount());
return out;
【讨论】:
把它做成oneliner有什么意义?我总是选择清晰的代码。作为一个自称是 VB.Net 爱好者(IIRC)的人,我认为您应该理解这种情绪? @sehe:我希望任何能胜任一半的 C++ 编码人员都能轻松理解这一行。与周围的其他东西相比,它相当温和。 @DevSolar 好吧,更清晰的版本缩短了约 30%,没有演员表,其他方面是等效的。因此,我的问题是:“把它做成单线笔有什么意义?” 注意:此方法将文件读入字符串流的缓冲区,然后将整个缓冲区复制到string
。 IE。需要两倍于其他一些选项的内存。 (没有办法移动缓冲区)。对于大文件,这将是一个巨大的损失,甚至可能导致分配失败。
@DanNissenbaum 你有些搞糊涂了。简洁在编程中确实很重要,但实现它的正确方法是将问题分解为多个部分,并将它们封装成独立的单元(函数、类等)。添加功能不会影响简洁性;恰恰相反。【参考方案2】:
最短变体:Live On Coliru
std::string str(std::istreambuf_iterator<char>ifs, );
它需要标头<iterator>
。
有报道称这种方法比预分配字符串和使用std::istream::read
慢。然而,在启用了优化的现代编译器上,情况似乎不再如此,尽管各种方法的相对性能似乎高度依赖于编译器。
【讨论】:
你能解释一下这个答案吗?它的效率如何,它是否一次读取一个字符的文件,无论如何要预先分配搅拌内存? @M.M 我读那个比较的方式,这个方法比纯 C++ 读入预分配缓冲区的方法慢。 你是对的,这是标题在代码示例下方而不是在其上方的情况:) 这个方法会不会多次触发内存重新分配? @coincheung 不幸的是,是的。如果您想避免内存分配,您需要手动缓冲读数。 C++ IO 流很垃圾。【参考方案3】:有关类似问题,请参阅 this answer。
为了您的方便,我转贴CTT的解决方案:
string readFile2(const string &fileName)
ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);
ifstream::pos_type fileSize = ifs.tellg();
ifs.seekg(0, ios::beg);
vector<char> bytes(fileSize);
ifs.read(bytes.data(), fileSize);
return string(bytes.data(), fileSize);
当对 Moby Dick (1.3M) 的文本进行平均 100 次运行时,此解决方案的执行时间比此处提供的其他答案快约 20%。对于可移植的 C++ 解决方案来说还不错,我想看看对文件进行映射的结果;)
【讨论】:
相关:各种方法的时间性能比较:Reading in an entire file at once in C++ 直到今天,我从未目睹过tellg() 报告非文件大小的结果。我花了几个小时才找到错误的来源。请不要使用tellg() 来获取文件大小。 ***.com/questions/22984956/… 您还需要检查空文件,因为您将通过&bytes[0]
取消引用 nullptr
@paxos1977> 说明您的程序在哪些系统上被定义为正确取决于您。照原样,它依赖于 C++ 不提供的保证,因此是错误的。如果它适用于确实提供此类保证的一组已知实现(如:记录为保证,而不仅仅是“今天在我拥有的那个版本上看起来还不错”),那么明确说明,否则会产生误导。
构建脆弱的代码库的完美推理,因为我有一天观察到的任何行为都“足够便携”。直到有人改变它。这不像我们有一次又一次的历史。 — 正确的工程是通过建立在保证之上来完成的,而不是探索现在似乎可行的任何事情并希望最好的结果。因此:此代码只是保证其假设的合理工程一种实现。 [注意:我没有谈论它今天是否有效,这无关紧要]【参考方案4】:
如果你有 C++17 (std::filesystem),也有这种方式(通过std::filesystem::file_size
而不是seekg
和tellg
获取文件大小):
#include <filesystem>
#include <fstream>
#include <string>
namespace fs = std::filesystem;
std::string readFile(fs::path path)
// Open the stream to 'lock' the file.
std::ifstream f(path, std::ios::in | std::ios::binary);
// Obtain the size of the file.
const auto sz = fs::file_size(path);
// Create a buffer.
std::string result(sz, '\0');
// Read the whole file into the buffer.
f.read(result.data(), sz);
return result;
注意:如果您的标准库尚未完全支持 C++17,您可能需要使用 <experimental/filesystem>
和 std::experimental::filesystem
。如果不支持non-const std::basic_string data,您可能还需要将result.data()
替换为&result[0]
。
【讨论】:
这可能会导致未定义的行为;以文本模式打开文件会产生与某些操作系统上的磁盘文件不同的流。 最初开发为boost::filesystem
所以如果你没有c++17也可以使用boost
用一个 API 打开一个文件并用另一个 API 获取它的大小似乎要求不一致和竞争条件。【参考方案5】:
使用
#include <iostream>
#include <sstream>
#include <fstream>
int main()
std::ifstream input("file.txt");
std::stringstream sstr;
while(input >> sstr.rdbuf());
std::cout << sstr.str() << std::endl;
或非常接近的东西。我没有打开 stdlib 引用来仔细检查自己。
是的,我知道我没有按要求编写 slurp
函数。
【讨论】:
这看起来不错,但无法编译。使其编译的更改将其简化为此页面上的其他答案。 ideone.com/EyhfWm 为什么是while循环? 同意。当operator>>
读入std::basic_streambuf
时,它将消耗(剩下的)输入流,因此循环是不必要的。【参考方案6】:
我没有足够的声誉直接评论使用 tellg()
的回复。
请注意,tellg()
可能会在出错时返回 -1。如果您将tellg()
的结果作为分配参数传递,您应该首先检查结果。
问题的一个例子:
...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...
在上面的例子中,如果tellg()
遇到错误,它将返回-1。有符号(即tellg()
的结果)和无符号(即vector<char>
构造函数的arg)之间的隐式转换将导致您的向量错误地分配非常 大量字节。 (可能是 4294967295 字节,即 4GB。)
修改 paxos1977 的答案以解决上述问题:
string readFile2(const string &fileName)
ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);
ifstream::pos_type fileSize = ifs.tellg();
if (fileSize < 0) <--- ADDED
return std::string(); <--- ADDED
ifs.seekg(0, ios::beg);
vector<char> bytes(fileSize);
ifs.read(&bytes[0], fileSize);
return string(&bytes[0], fileSize);
【讨论】:
不仅如此,tellg()
不返回大小而是返回一个令牌。许多系统使用字节偏移量作为标记,但这不能保证,有些系统不保证。以this answer 为例。【参考方案7】:
此解决方案将错误检查添加到基于 rdbuf() 的方法中。
std::string file_to_string(const std::string& file_name)
std::ifstream file_streamfile_name;
if (file_stream.fail())
// Error opening file.
std::ostringstream str_stream;
file_stream >> str_stream.rdbuf(); // NOT str_stream << file_stream.rdbuf()
if (file_stream.fail() && !file_stream.eof())
// Error reading file.
return str_stream.str();
我添加这个答案是因为在原始方法中添加错误检查并不像您期望的那么简单。原始方法使用 stringstream 的插入运算符 (str_stream << file_stream.rdbuf()
)。问题是这会在没有插入字符时设置字符串流的故障位。这可能是由于错误,也可能是由于文件为空。如果您通过检查故障位来检查故障,则在读取空文件时会遇到误报。您如何区分插入任何字符的合法失败和由于文件为空而导致插入任何字符的“失败”?
您可能会考虑显式检查空文件,但这需要更多代码和相关的错误检查。
检查失败条件 str_stream.fail() && !str_stream.eof()
不起作用,因为插入操作没有设置 eofbit(在 ostringstream 和 ifstream 上)。
因此,解决方案是更改操作。不要使用 ostringstream 的插入运算符 (>),它确实设置了 eofbit。然后检查失败情况file_stream.fail() && !file_stream.eof()
。
重要的是,当file_stream >> str_stream.rdbuf()
遇到合法故障时,它不应该设置 eofbit(根据我对规范的理解)。这意味着上述检查足以检测合法故障。
【讨论】:
【参考方案8】:这是一个使用新文件系统库的版本,具有相当强大的错误检查功能:
#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
namespace fs = std::filesystem;
std::string loadFile(const char *const name);
std::string loadFile(const std::string &name);
std::string loadFile(const char *const name)
fs::path filepath(fs::absolute(fs::path(name)));
std::uintmax_t fsize;
if (fs::exists(filepath))
fsize = fs::file_size(filepath);
else
throw(std::invalid_argument("File not found: " + filepath.string()));
std::ifstream infile;
infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try
infile.open(filepath.c_str(), std::ios::in | std::ifstream::binary);
catch (...)
std::throw_with_nested(std::runtime_error("Can't open input file " + filepath.string()));
std::string fileStr;
try
fileStr.resize(fsize);
catch (...)
std::stringstream err;
err << "Can't resize to " << fsize << " bytes";
std::throw_with_nested(std::runtime_error(err.str()));
infile.read(fileStr.data(), fsize);
infile.close();
return fileStr;
std::string loadFile(const std::string &name) return loadFile(name.c_str()); ;
【讨论】:
infile.open
也可以接受std::string
而无需使用.c_str()
进行转换
filepath
不是std::string
,而是std::filesystem::path
。原来std::ifstream::open
也可以接受其中之一。
@DavidG, std::filesystem::path
可隐式转换为 std::string
根据 cppreference.com,std::ifstream
上接受 std::filesystem::path
的 ::open
成员函数的运行就像在路径上调用了 ::c_str()
方法一样。路径的底层::value_type
是POSIX下的char
。【参考方案9】:
由于这似乎是一个广泛使用的实用程序,我的方法是搜索并更喜欢已经可用的库而不是手工制作的解决方案,特别是如果你的项目中已经链接了 boost 库(链接器标志 -lboost_system -lboost_filesystem)。 Here (and older boost versions too),boost 提供了一个 load_string_file 实用程序:
#include <iostream>
#include <string>
#include <boost/filesystem/string_file.hpp>
int main()
std::string result;
boost::filesystem::load_string_file("aFileName.xyz", result);
std::cout << result.size() << std::endl;
作为一个优点,这个函数不会寻找整个文件来确定大小,而是在内部使用 stat()。不过,作为一个可能可以忽略不计的缺点,人们可以通过查看源代码轻松推断:字符串被不必要地调整为 '\0'
字符,这些字符被文件内容重写。
【讨论】:
【参考方案10】:这样的事情应该不会太糟糕:
void slurp(std::string& data, const std::string& filename, bool is_binary)
std::ios_base::openmode openmode = ios::ate | ios::in;
if (is_binary)
openmode |= ios::binary;
ifstream file(filename.c_str(), openmode);
data.clear();
data.reserve(file.tellg());
file.seekg(0, ios::beg);
data.append(istreambuf_iterator<char>(file.rdbuf()),
istreambuf_iterator<char>());
这里的优点是我们先做保留,这样我们就不必在读入内容时增加字符串。缺点是我们逐个字符地进行。更智能的版本可以抓取整个 read buf,然后调用 underflow。
【讨论】:
您应该检查使用 std::vector 进行初始读取而不是字符串的代码版本。快得多。【参考方案11】:您可以使用 'std::getline' 函数,并指定 'eof' 作为分隔符。生成的代码虽然有点模糊:
std::string data;
std::ifstream in( "test.txt" );
std::getline( in, data, std::string::traits_type::to_char_type(
std::string::traits_type::eof() ) );
【讨论】:
我刚刚对此进行了测试,它似乎比获取文件大小并将整个文件大小的读取调用到缓冲区中要慢得多。大约慢 12 倍。 只有在您的文件中没有“eof”(例如 0x00、0xff、...)字符的情况下,这才有效。如果有,您将只读取文件的一部分。【参考方案12】:#include <string>
#include <sstream>
using namespace std;
string GetStreamAsString(const istream& in)
stringstream out;
out << in.rdbuf();
return out.str();
string GetFileAsString(static string& filePath)
ifstream stream;
try
// Set to throw on failure
stream.exceptions(fstream::failbit | fstream::badbit);
stream.open(filePath);
catch (system_error& error)
cerr << "Failed to open '" << filePath << "'\n" << error.code().message() << endl;
return "Open fail";
return GetStreamAsString(stream);
用法:
const string logAsString = GetFileAsString(logFilePath);
【讨论】:
【参考方案13】:基于 CTT 解决方案的更新功能:
#include <string>
#include <fstream>
#include <limits>
#include <string_view>
std::string readfile(const std::string_view path, bool binaryMode = true)
std::ios::openmode openmode = std::ios::in;
if(binaryMode)
openmode |= std::ios::binary;
std::ifstream ifs(path.data(), openmode);
ifs.ignore(std::numeric_limits<std::streamsize>::max());
std::string data(ifs.gcount(), 0);
ifs.seekg(0);
ifs.read(data.data(), data.size());
return data;
有两个重要的区别:
tellg()
不保证返回自文件开头以来的偏移量(以字节为单位)。相反,正如 Puzomor Croatia 所指出的,它更像是一个可以在 fstream 调用中使用的令牌。 gcount()
然而确实返回上次提取的未格式化字节的数量。因此,我们打开文件,使用ignore()
提取并丢弃其所有内容以获取文件的大小,并基于此构造输出字符串。
其次,我们避免了通过直接写入字符串来将文件数据从std::vector<char>
复制到std::string
。
就性能而言,这应该是绝对最快的,提前分配适当大小的字符串并调用一次read()
。有趣的是,在 gcc 上使用 ignore()
和 countg()
而不是 ate
和 tellg()
会一点一点地编译为 almost the same thing。
【讨论】:
这段代码不起作用,我得到的是空字符串。我认为您想要ifs.seekg(0)
而不是ifs.clear()
(然后它可以工作)。
std::string::data()
在 C++17 之前返回 const char*。【参考方案14】:
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
main()
fstream file;
//Open a file
file.open("test.txt");
string copy,temp;
//While loop to store whole document in copy string
//Temp reads a complete line
//Loop stops until temp reads the last line of document
while(getline(file,temp))
//add new line text in copy
copy+=temp;
//adds a new line
copy+="\n";
//Display whole document
cout<<copy;
//close the document
file.close();
【讨论】:
请添加描述。 请访问how to answer a question查看。 如果你想将它存储到一个字符串中。如果队列未满,我会添加一些描述。 副本是一个字符串变量,在代码中使用保存整个文本,您可以将它们分配给另一个变量。【参考方案15】:这是我使用的函数,在处理大文件 (1GB+) 时,由于某种原因,std::ifstream::read() 比 std::ifstream::rdbuf( ) 当你知道文件大小时,所以整个“首先检查文件大小”的事情实际上是一个速度优化
#include <string>
#include <fstream>
#include <sstream>
std::string file_get_contents(const std::string &$filename)
std::ifstream file($filename, std::ifstream::binary);
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
file.seekg(0, std::istream::end);
const std::streampos ssize = file.tellg();
if (ssize < 0)
// can't get size for some reason, fallback to slower "just read everything"
// because i dont trust that we could seek back/fourth in the original stream,
// im creating a new stream.
std::ifstream file($filename, std::ifstream::binary);
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
std::ostringstream ss;
ss << file.rdbuf();
return ss.str();
file.seekg(0, std::istream::beg);
std::string result(size_t(ssize), 0);
file.read(&result[0], std::streamsize(ssize));
return result;
【讨论】:
std::string result(size_t(ssize), 0);
用字符 0(null 或 \0)填充字符串,根据 OP 的问题,这可能被认为是“不必要的开销”
@MarcheRemi 确实,当您只需要 malloc() 时,它基本上就像使用 calloc()。也就是说,创建一串未初始化的字节是really hard - 我认为你可以提供一个自定义分配器来真正实现它,但似乎还没有人弄清楚到底是怎么回事。
美元符号是怎么回事?
@einpoklum 是...继承自 php 的 api,它是 php 的 file_get_contents 的(不完整)port【参考方案16】:
就性能而言,我没有发现比下面的代码更快的东西。
std::string readAllText(std::string const &path)
assert(path.c_str() != NULL);
FILE *stream = fopen(path.c_str(), "r");
assert(stream != NULL);
fseek(stream, 0, SEEK_END);
long stream_size = ftell(stream);
fseek(stream, 0, SEEK_SET);
void *buffer = malloc(stream_size);
fread(buffer, stream_size, 1, stream);
assert(ferror(stream) == 0);
fclose(stream);
std::string text((const char *)buffer, stream_size);
assert(buffer != NULL);
free((void *)buffer);
return text;
【讨论】:
这当然可以加快速度。一方面,使用rb
(二进制)模式而不是r
(文本)模式。并摆脱malloc()
,你不需要它。您可以将resize()
和std::string
然后fread()
直接放入其内存缓冲区。无需malloc()
一个缓冲区,然后将其复制到std::string
。
@RemyLebeau resize()
毫无意义地 0 初始化内存。当然,仍然比完整副本快,但同样毫无意义。至于这篇文章:使用断言来检查fopen()
的结果是直截了当的邪恶和错误。必须始终检查它,而不仅仅是在调试版本中。有了这个实现,一个简单的错字会导致未定义的行为(当然,在实践中是一个段错误,但这不是重点)。【参考方案17】:
您可以使用我为此开发的rst C++ 库:
#include "rst/files/file_utils.h"
std::filesystem::path path = ...; // Path to a file.
rst::StatusOr<std::string> content = rst::ReadFile(path);
if (content.err())
// Handle error.
std::cout << *content << ", " << content->size() << std::endl;
【讨论】:
【参考方案18】:我知道这是一个非常古老的问题,有很多答案,但没有一个人提到我认为最明显的方法。是的,我知道这是 C++,使用 libc 是邪恶的和错误的或其他什么,但对此很疯狂。使用 libc 很好,尤其是对于这种简单的事情。
本质上:只需打开文件,获取它的大小(不一定按此顺序),然后阅读它。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>
static constexpr char const filename[] = "foo.bar";
int main(void)
FILE *fp = ::fopen(filename, "rb");
if (!fp)
::perror("fopen");
::exit(1);
struct stat st;
if (::fstat(fileno(fp), &st) == (-1))
::perror("fstat");
::exit(1);
// You could simply allocate a buffer here and use std::string_view, or
// even allocate a buffer and copy it to a std::string. Creating a
// std::string and setting its size is simplest, but will pointlessly
// initialize the buffer to 0. You can't win sometimes.
std::string str;
str.reserve(st.st_size + 1U);
str.resize(st.st_size);
::fread(str.data(), 1, st.st_size, fp);
str[st.st_size] = '\0';
::fclose(fp);
除了(实际上)完全可移植之外,这似乎并不比其他一些解决方案更糟糕。当然,也可以抛出异常而不是立即退出。调整 std::string
的大小总是 0 会初始化它,这让我非常恼火,但它无济于事。
请注意,这只适用于为 C++17 及更高版本编写的代码。早期版本(应该)不允许编辑std::string::data()
。如果使用早期版本,请考虑使用 std::string_view
或简单地复制原始缓冲区。
【讨论】:
什么是 std::string? @einpoklum 我认为 op 可以从 C 字符串中自己制作一个。经过反思,您可能是对的,该问题专门询问如何将其读入std::string
。已更新。【参考方案19】:
#include <string>
#include <fstream>
int main()
std::string fileLocation = "C:\\Users\\User\\Desktop\\file.txt";
std::ifstream file(fileLocation, std::ios::in | std::ios::binary);
std::string data;
if(file.is_open())
std::getline(file, data, '\0');
file.close();
【讨论】:
好像是Martin Cote's 2008 answer的变种,它使用EOF? (并且与该答案的 cmets 中所写的警告相同。)还请尝试提供比代码块更多的信息,请参阅How do I write a good answer?。【参考方案20】:永远不要写入 std::string 的 const char * 缓冲区。永远不能!这样做是一个巨大的错误。
为 std::string 中的整个字符串保留() 空间,从文件中读取合理大小的块到缓冲区中,然后 append() 它。块的大小取决于您的输入文件大小。我很确定所有其他可移植和符合 STL 的机制都会做同样的事情(但可能看起来更漂亮)。
【讨论】:
从C++11开始保证直接写入std::string
缓冲区是可以的;我相信它在之前的所有实际实现中都能正常工作
从 C++17 开始,我们甚至有了非常量 std::string::data()
方法,可以直接修改字符串缓冲区,而无需借助 &str[0]
之类的技巧。
同意@zett42 这个答案实际上是不正确的【参考方案21】:
我知道我迟到了,但现在(2021 年)在我的机器上,这是我测试过的最快的实现:
#include <fstream>
#include <string>
bool fileRead( std::string &contents, const std::string &path )
contents.clear();
if( path.empty())
return false;
std::ifstream stream( path );
if( !stream )
return false;
stream >> contents;
return true;
【讨论】:
……你是怎么测试的?!因为这肯定不是最快的实现,而且它不会读取整个文件。以上是关于如何在 C++ 中将整个文件读入 std::string?的主要内容,如果未能解决你的问题,请参考以下文章
Windows C++ API:如何将整个二进制文件读入缓冲区?