首页 > 解决方案 > 读取二进制文件的惯用 C++17 标准方法是什么?

问题描述

通常我只会使用 C 样式文件 IO,但我正在尝试现代 C++ 方法,包括使用 C++17 特定功能std::bytestd::filesystem.

将整个文件读入内存,传统方法:

#include <stdio.h>
#include <stdlib.h>

char *readFileData(char *path)
{
    FILE *f;
    struct stat fs;
    char *buf;

    stat(path, &fs);
    buf = (char *)malloc(fs.st_size);

    f = fopen(path, "rb");
    fread(buf, fs.st_size, 1, f);
    fclose(f);

    return buf;
}

将整个文件读入内存,现代方法:

#include <filesystem>
#include <fstream>
#include <string>
using namespace std;
using namespace std::filesystem;

auto readFileData(string path)
{
    auto fileSize = file_size(path);
    auto buf = make_unique<byte[]>(fileSize);
    basic_ifstream<byte> ifs(path, ios::binary);
    ifs.read(buf.get(), fileSize);
    return buf;
}

这看起来对吗?这可以改进吗?

标签: c++fileioc++17

解决方案


除非您正在阅读实际的文本文档,否则我个人更喜欢std::vector<std::byte>使用。std::string问题make_unique<byte[]>(fileSize);是您会立即丢失数据的大小,并且必须将其放在单独的变量中。std::vector<std::byte>它可能比它不会零初始化的给定速度快一小部分。但我认为这可能总是被从磁盘读取所花费的时间所掩盖。

所以对于一个二进制文件,我使用这样的东西:

std::vector<std::byte> load_file(std::string const& filepath)
{
    std::ifstream ifs(filepath, std::ios::binary|std::ios::ate);

    if(!ifs)
        throw std::runtime_error(filepath + ": " + std::strerror(errno));

    auto end = ifs.tellg();
    ifs.seekg(0, std::ios::beg);

    auto size = std::size_t(end - ifs.tellg());

    if(size == 0) // avoid undefined behavior 
        return {}; 

    std::vector<std::byte> buffer(size);

    if(!ifs.read((char*)buffer.data(), buffer.size()))
        throw std::runtime_error(filepath + ": " + std::strerror(errno));

    return buffer;
}

这是我所知道的最快的方法。它也避免了在确定文件中数据大小时的常见错误,因为ifs.tellg()不一定与打开文件末尾的文件大小相同,ifs.seekg(0)理论上也不是定位文件开头的正确方法(即使它在大多数地方都适用)。

来自的错误消息std::strerror(errno)保证可以在POSIX系统上运行(应该包括 Microsoft,但不确定)。

显然,您可以根据std::filesystem::path const& filepath需要使用std::string

此外,特别是对于 pre C++17,您可以使用std::vector<unsigned char>,或者std::vector<char>如果您没有或不想使用std::byte.


推荐阅读