如何将文件读入a std::string
,即一次读取整个文件?
文本或二进制模式应由调用者指定.该解决方案应符合标准,便携且高效.它不应该不必要地复制字符串的数据,它应该避免在读取字符串时重新分配内存.
实现此目的的一种方法是统计文件大小,调整大小std::string
和fread()
进入std::string
's const_cast<char*>()
' data()
.这要求std::string
数据是连续的,这是标准不需要的,但似乎是所有已知实现的情况.更糟糕的是,如果在文本模式下读取文件,则其std::string
大小可能与文件大小不同.
一个完全正确的,符合标准的和便携式解决方案,可以构建使用std::ifstream
的rdbuf()
进入std::ostringstream
,并从那里进入std::string
.但是,这可能会复制字符串数据和/或不必要地重新分配内存.所有相关的标准库实现是否足够智能以避免所有不必要的开销?还有另一种方法吗?我是否错过了一些已经提供所需功能的隐藏Boost功能?
请显示您的建议如何实施.
void slurp(std::string& data, bool is_binary)
Run Code Online (Sandbox Code Playgroud)
考虑到上面的讨论.
Kon*_*lph 129
并且最快(我知道,折扣内存映射文件):
std::string str(static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str());
Run Code Online (Sandbox Code Playgroud)
这需要<sstream>
字符串流的附加标头.(这static_cast
是必要的,因为operator <<
返回一个普通的老,ostream&
但我们知道,实际上它是一个stringstream&
所以演员是安全的.)
拆分成多行,将临时值移动到变量中,我们得到一个更易读的代码:
std::string slurp(std::ifstream& in) {
std::stringstream sstr;
sstr << in.rdbuf();
return sstr.str();
}
Run Code Online (Sandbox Code Playgroud)
或者,再次在一行中:
std::string slurp(std::ifstream& in) {
return static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str();
}
Run Code Online (Sandbox Code Playgroud)
pax*_*977 51
在类似的问题上看到这个答案.
为了您的方便,我正在重新发布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);
}
Run Code Online (Sandbox Code Playgroud)
与针对Moby Dick(1.3M)文本的平均100次运行相比,此解决方案的执行时间比此处提供的其他答案快20%.对于便携式C++解决方案来说还不错,我希望看到mmap的文件结果;)
Kon*_*lph 45
最短的变种: Live On Coliru
std::string str(std::istreambuf_iterator<char>{ifs}, {});
Run Code Online (Sandbox Code Playgroud)
它需要标题<iterator>
.
有一些报道说这种方法比预分配字符串和使用要慢std::istream::read
.但是,在启用了优化的现代编译器上,似乎不再是这种情况,尽管各种方法的相对性能似乎高度依赖于编译器.
Ben*_*ins 17
使用
#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;
}
Run Code Online (Sandbox Code Playgroud)
或者非常接近的东西.我没有打开stdlib引用来仔细检查自己.
是的,我知道我没有slurp
按要求编写该功能.
Ric*_*ter 12
我没有足够的声誉直接评论使用的回复tellg()
.
请注意,tellg()
错误时可以返回-1.如果您将结果tellg()
作为分配参数传递,则应首先检查结果.
问题的一个例子:
...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,如果tellg()
遇到错误,它将返回-1.签署(IE的结果之间的隐式转换tellg()
)和无符号(即ARG的vector<char>
构造函数)将导致您的载体错误分配一个非常大的字节数.(可能是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);
}
Run Code Online (Sandbox Code Playgroud)
如果你有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 };
// Obtain the size of the file.
const auto sz = fs::file_size(path);
// Create a buffer.
std::string result(sz, ' ');
// Read the whole file into the buffer.
f.read(result.data(), sz);
return result;
}
Run Code Online (Sandbox Code Playgroud)
注意:您可能需要使用<experimental/filesystem>
,std::experimental::filesystem
如果您的标准库尚未完全支持C++ 17.您可能还需要更换result.data()
与&result[0]
如果它不支持非const的std :: basic_string的数据.
此解决方案向基于 rdbuf() 的方法添加了错误检查。
std::string file_to_string(const std::string& file_name)
{
std::ifstream file_stream{file_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();
}
Run Code Online (Sandbox Code Playgroud)
我添加这个答案是因为向原始方法添加错误检查并不像您期望的那么简单。原始方法使用 stringstream 的插入运算符 ( str_stream << file_stream.rdbuf()
)。问题在于,当没有插入字符时,这会设置字符串流的故障位。这可能是由于错误,也可能是由于文件为空。如果通过检查故障位来检查故障,则在读取空文件时会遇到误报。您如何消除由于文件为空而导致插入任何字符的合法失败和“失败”插入任何字符的歧义?
您可能会考虑明确检查空文件,但这需要更多的代码和相关的错误检查。
检查失败条件str_stream.fail() && !str_stream.eof()
不起作用,因为插入操作没有设置 eofbit(在 ostringstream 和 ifstream 上)。
所以,解决办法是改变操作。不要使用 ostringstream 的插入运算符 (<<),而是使用 ifstream 的提取运算符 (>>),它设置 eofbit。然后检查故障情况file_stream.fail() && !file_stream.eof()
。
重要的是,当file_stream >> str_stream.rdbuf()
遇到合法的故障时,它不应该设置 eofbit(根据我对规范的理解)。这意味着上述检查足以检测合法故障。
像这样的事情不应该太糟糕:
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>());
}
Run Code Online (Sandbox Code Playgroud)
这里的优点是我们先进行保留,这样我们就不必在读取内容时增加字符串。缺点是我们逐个字符地进行。更智能的版本可以获取整个读取缓冲区,然后调用下溢。
这是一个使用新文件系统库的版本,具有相当强大的错误检查功能:
#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()); };
Run Code Online (Sandbox Code Playgroud)
由于这似乎是一个广泛使用的实用程序,因此我的方法是搜索并优先选择已有的库而不是手工制作的解决方案,特别是如果您的项目中已经链接了 boost 库(链接器标志 -lboost_system -lboost_filesystem)。在这里(以及旧的 boost 版本),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;
}
Run Code Online (Sandbox Code Playgroud)
作为一个优势,此函数不会寻找整个文件来确定大小,而是在内部使用 stat()。然而,作为一个可能可以忽略不计的缺点,通过检查源代码可以很容易地推断出:字符串被不必要地调整'\0'
为由文件内容重写的字符。
归档时间: |
|
查看次数: |
59948 次 |
最近记录: |