【问题标题】:Fast way to write data from a std::vector to a text file将数据从 std::vector 写入文本文件的快速方法
【发布时间】:2017-02-06 19:13:05
【问题描述】:

我目前将一组双精度从向量写入文本文件,如下所示:

std::ofstream fout;
fout.open("vector.txt");

for (l = 0; l < vector.size(); l++)
    fout << std::setprecision(10) << vector.at(l) << std::endl;

fout.close();

但这需要很长时间才能完成。有没有更快或更有效的方法来做到这一点?我很想看到并学习它。

【问题讨论】:

  • 你的向量有多大? “一段时间”有多长?
  • 你的向量包含什么?
  • 它像 300k 双倍,并且需要将近 3 分钟。
  • 我猜想通过使用更快的双精度到字符串转换、硬编码格式和精度可以达到最大的加速。一旦我编写了自己的 ftoa 函数(对于某些浮点范围),它的速度提高了 20 倍。 github.com/rudimeier/atem/blob/master/src/ftoa.c
  • @FirstStep:CR 用于审查代码。 OP正在询问如何更有效地做某事。那将是 CR 的主题。此外,如果某些事情已经有了答案,通常不建议迁移。

标签: c++ std ofstream


【解决方案1】:
std::ofstream fout("vector.txt");
fout << std::setprecision(10);

for(auto const& x : vector)
    fout << x << '\n';

理论上,我更改的所有内容在您的代码版本中性能更差,但 std::endl was the real killer. std::vector::at(带有边界检查,您不需要)将是第二个,然后是您没有使用迭代器的事实。

当你可以一步完成时,为什么要默认构造一个std::ofstream,然后调用open?当 RAII(析构函数)为您处理它时,为什么要调用 close?也可以调用

fout << std::setprecision(10)

只有一次,在循环之前。

如以下评论中所述,如果您的向量是基本类型的元素,则使用for(auto x : vector) 可能会获得更好的性能。测量运行时间/检查装配输出。


只是指出另一件引起我注意的事情,这个:

for(l = 0; l < vector.size(); l++)

这是什么l?为什么要在循环之外声明它?看来您在外部范围内不需要它,所以不要。还有post-increment

结果:

for(size_t l = 0; l < vector.size(); ++l)

很抱歉,我对这篇文章进行了代码审查。

【讨论】:

  • 另外,鉴于它是一个双精度向量,使用非引用迭代变量 (for (auto x : vector)) 可能会更快——它用取消引用换取双拷贝。
  • 仅供参考:我的评论中提到的项目确实在我的机器上产生了更好的性能,但它的规模约为每 10,000,000 个项目 1 个刻度。如果您的向量包含更复杂的数据(或超过 64 位的数据),那么@LogicStuff 的答案会更好。
  • 流操纵器改变持久状态,对吧?因此,您可以在循环之前将精度设置为 10,而不是每次都设置为 10。诚然,这可能比隐藏在std::endl 中的额外刷新要小得多。
  • 范围 for 循环还避免了 vector::at 调用,这些调用是边界检查的,因此可能比 vector::operator[] 慢,范围 for 循环也避免了这种情况。
  • 我认为后增量不再是问题,因为 C++11?
【解决方案2】:

你的算法有两个部分:

  1. 将双精度数序列化为字符串或字符缓冲区。

  2. 将结果写入文件。

使用 sprintf 或 fmt 可以改进第一项 (> 20%)。第二项可以通过将结果缓存到缓冲区或在将结果写入输出文件之前扩展输出文件流缓冲区大小来加快速度。你不应该使用 std::endl 因为it is much slower than using "\n"。如果您仍然想让它更快,那么以二进制格式写入数据。下面是我的完整代码示例,其中包括我提出的解决方案和来自 Edgar Rokyan 的解决方案。我还在测试代码中包含了 Ben Voigt 和 Matthieu M 的建议。

#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <vector>

// https://github.com/fmtlib/fmt
#include "fmt/format.h"

// http://uscilab.github.io/cereal/
#include "cereal/archives/binary.hpp"
#include "cereal/archives/json.hpp"
#include "cereal/archives/portable_binary.hpp"
#include "cereal/archives/xml.hpp"
#include "cereal/types/string.hpp"
#include "cereal/types/vector.hpp"

// https://github.com/DigitalInBlue/Celero
#include "celero/Celero.h"

template <typename T> const char* getFormattedString();
template<> const char* getFormattedString<double>(){return "%g\n";}
template<> const char* getFormattedString<float>(){return "%g\n";}
template<> const char* getFormattedString<int>(){return "%d\n";}
template<> const char* getFormattedString<size_t>(){return "%lu\n";}


namespace {
    constexpr size_t LEN = 32;

    template <typename T> std::vector<T> create_test_data(const size_t N) {
        std::vector<T> data(N);
        for (size_t idx = 0; idx < N; ++idx) {
            data[idx] = idx;
        }
        return data;
    }

    template <typename Iterator> auto toVectorOfChar(Iterator begin, Iterator end) {
        char aLine[LEN];
        std::vector<char> buffer;
        buffer.reserve(std::distance(begin, end) * LEN);
        const char* fmtStr = getFormattedString<typename std::iterator_traits<Iterator>::value_type>();
        std::for_each(begin, end, [&buffer, &aLine, &fmtStr](const auto value) {
            sprintf(aLine, fmtStr, value);
            for (size_t idx = 0; aLine[idx] != 0; ++idx) {
                buffer.push_back(aLine[idx]);
            }
        });
        return buffer;
    }

    template <typename Iterator>
    auto toStringStream(Iterator begin, Iterator end, std::stringstream &buffer) {
        char aLine[LEN];
        const char* fmtStr = getFormattedString<typename std::iterator_traits<Iterator>::value_type>();
        std::for_each(begin, end, [&buffer, &aLine, &fmtStr](const auto value) {            
            sprintf(aLine, fmtStr, value);
            buffer << aLine;
        });
    }

    template <typename Iterator> auto toMemoryWriter(Iterator begin, Iterator end) {
        fmt::MemoryWriter writer;
        std::for_each(begin, end, [&writer](const auto value) { writer << value << "\n"; });
        return writer;
    }

    // A modified version of the original approach.
    template <typename Container>
    void original_approach(const Container &data, const std::string &fileName) {
        std::ofstream fout(fileName);
        for (size_t l = 0; l < data.size(); l++) {
            fout << data[l] << std::endl;
        }
        fout.close();
    }

    // Replace std::endl by "\n"
    template <typename Iterator>
    void improved_original_approach(Iterator begin, Iterator end, const std::string &fileName) {
        std::ofstream fout(fileName);
        const size_t len = std::distance(begin, end) * LEN;
        std::vector<char> buffer(len);
        fout.rdbuf()->pubsetbuf(buffer.data(), len);
        for (Iterator it = begin; it != end; ++it) {
            fout << *it << "\n";
        }
        fout.close();
    }

    //
    template <typename Iterator>
    void edgar_rokyan_solution(Iterator begin, Iterator end, const std::string &fileName) {
        std::ofstream fout(fileName);
        std::copy(begin, end, std::ostream_iterator<double>(fout, "\n"));
    }

    // Cache to a string stream before writing to the output file
    template <typename Iterator>
    void stringstream_approach(Iterator begin, Iterator end, const std::string &fileName) {
        std::stringstream buffer;
        for (Iterator it = begin; it != end; ++it) {
            buffer << *it << "\n";
        }

        // Now write to the output file.
        std::ofstream fout(fileName);
        fout << buffer.str();
        fout.close();
    }

    // Use sprintf
    template <typename Iterator>
    void sprintf_approach(Iterator begin, Iterator end, const std::string &fileName) {
        std::stringstream buffer;
        toStringStream(begin, end, buffer);
        std::ofstream fout(fileName);
        fout << buffer.str();
        fout.close();
    }

    // Use fmt::MemoryWriter (https://github.com/fmtlib/fmt)
    template <typename Iterator>
    void fmt_approach(Iterator begin, Iterator end, const std::string &fileName) {
        auto writer = toMemoryWriter(begin, end);
        std::ofstream fout(fileName);
        fout << writer.str();
        fout.close();
    }

    // Use std::vector<char>
    template <typename Iterator>
    void vector_of_char_approach(Iterator begin, Iterator end, const std::string &fileName) {
        std::vector<char> buffer = toVectorOfChar(begin, end);
        std::ofstream fout(fileName);
        fout << buffer.data();
        fout.close();
    }

    // Use cereal (http://uscilab.github.io/cereal/).
    template <typename Container, typename OArchive = cereal::BinaryOutputArchive>
    void use_cereal(Container &&data, const std::string &fileName) {
        std::stringstream buffer;
        {
            OArchive oar(buffer);
            oar(data);
        }

        std::ofstream fout(fileName);
        fout << buffer.str();
        fout.close();
    }
}

// Performance test input data.
constexpr int NumberOfSamples = 5;
constexpr int NumberOfIterations = 2;
constexpr int N = 3000000;
const auto double_data = create_test_data<double>(N);
const auto float_data = create_test_data<float>(N);
const auto int_data = create_test_data<int>(N);
const auto size_t_data = create_test_data<size_t>(N);

CELERO_MAIN

BASELINE(DoubleVector, original_approach, NumberOfSamples, NumberOfIterations) {
    const std::string fileName("origsol.txt");
    original_approach(double_data, fileName);
}

BENCHMARK(DoubleVector, improved_original_approach, NumberOfSamples, NumberOfIterations) {
    const std::string fileName("improvedsol.txt");
    improved_original_approach(double_data.cbegin(), double_data.cend(), fileName);
}

BENCHMARK(DoubleVector, edgar_rokyan_solution, NumberOfSamples, NumberOfIterations) {
    const std::string fileName("edgar_rokyan_solution.txt");
    edgar_rokyan_solution(double_data.cbegin(), double_data.end(), fileName);
}

BENCHMARK(DoubleVector, stringstream_approach, NumberOfSamples, NumberOfIterations) {
    const std::string fileName("stringstream.txt");
    stringstream_approach(double_data.cbegin(), double_data.cend(), fileName);
}

BENCHMARK(DoubleVector, sprintf_approach, NumberOfSamples, NumberOfIterations) {
    const std::string fileName("sprintf.txt");
    sprintf_approach(double_data.cbegin(), double_data.cend(), fileName);
}

BENCHMARK(DoubleVector, fmt_approach, NumberOfSamples, NumberOfIterations) {
    const std::string fileName("fmt.txt");
    fmt_approach(double_data.cbegin(), double_data.cend(), fileName);
}

BENCHMARK(DoubleVector, vector_of_char_approach, NumberOfSamples, NumberOfIterations) {
    const std::string fileName("vector_of_char.txt");
    vector_of_char_approach(double_data.cbegin(), double_data.cend(), fileName);
}

BENCHMARK(DoubleVector, use_cereal, NumberOfSamples, NumberOfIterations) {
    const std::string fileName("cereal.bin");
    use_cereal(double_data, fileName);
}

// Benchmark double vector
BASELINE(DoubleVectorConversion, toStringStream, NumberOfSamples, NumberOfIterations) {
    std::stringstream output;
    toStringStream(double_data.cbegin(), double_data.cend(), output);
}

BENCHMARK(DoubleVectorConversion, toMemoryWriter, NumberOfSamples, NumberOfIterations) {
    celero::DoNotOptimizeAway(toMemoryWriter(double_data.cbegin(), double_data.cend()));
}

BENCHMARK(DoubleVectorConversion, toVectorOfChar, NumberOfSamples, NumberOfIterations) {
    celero::DoNotOptimizeAway(toVectorOfChar(double_data.cbegin(), double_data.cend()));
}

// Benchmark float vector
BASELINE(FloatVectorConversion, toStringStream, NumberOfSamples, NumberOfIterations) {
    std::stringstream output;
    toStringStream(float_data.cbegin(), float_data.cend(), output);
}

BENCHMARK(FloatVectorConversion, toMemoryWriter, NumberOfSamples, NumberOfIterations) {
    celero::DoNotOptimizeAway(toMemoryWriter(float_data.cbegin(), float_data.cend()));
}

BENCHMARK(FloatVectorConversion, toVectorOfChar, NumberOfSamples, NumberOfIterations) {
    celero::DoNotOptimizeAway(toVectorOfChar(float_data.cbegin(), float_data.cend()));
}

// Benchmark int vector
BASELINE(int_conversion, toStringStream, NumberOfSamples, NumberOfIterations) {
    std::stringstream output;
    toStringStream(int_data.cbegin(), int_data.cend(), output);
}

BENCHMARK(int_conversion, toMemoryWriter, NumberOfSamples, NumberOfIterations) {
    celero::DoNotOptimizeAway(toMemoryWriter(int_data.cbegin(), int_data.cend()));
}

BENCHMARK(int_conversion, toVectorOfChar, NumberOfSamples, NumberOfIterations) {
    celero::DoNotOptimizeAway(toVectorOfChar(int_data.cbegin(), int_data.cend()));
}

// Benchmark size_t vector
BASELINE(size_t_conversion, toStringStream, NumberOfSamples, NumberOfIterations) {
    std::stringstream output;
    toStringStream(size_t_data.cbegin(), size_t_data.cend(), output);
}

BENCHMARK(size_t_conversion, toMemoryWriter, NumberOfSamples, NumberOfIterations) {
    celero::DoNotOptimizeAway(toMemoryWriter(size_t_data.cbegin(), size_t_data.cend()));
}

BENCHMARK(size_t_conversion, toVectorOfChar, NumberOfSamples, NumberOfIterations) {
    celero::DoNotOptimizeAway(toVectorOfChar(size_t_data.cbegin(), size_t_data.cend()));
}

以下是在我的 Linux 机器中使用 clang-3.9.1 和 -O3 标志获得的性能结果。我使用Celero 收集所有性能结果。

Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
     Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  | 
-----------------------------------------------------------------------------------------------------------------------------------------------
DoubleVector    | original_approa | Null            |              10 |               4 |         1.00000 |   3650309.00000 |            0.27 | 
DoubleVector    | improved_origin | Null            |              10 |               4 |         0.47828 |   1745855.00000 |            0.57 | 
DoubleVector    | edgar_rokyan_so | Null            |              10 |               4 |         0.45804 |   1672005.00000 |            0.60 | 
DoubleVector    | stringstream_ap | Null            |              10 |               4 |         0.41514 |   1515377.00000 |            0.66 | 
DoubleVector    | sprintf_approac | Null            |              10 |               4 |         0.35436 |   1293521.50000 |            0.77 | 
DoubleVector    | fmt_approach    | Null            |              10 |               4 |         0.34916 |   1274552.75000 |            0.78 | 
DoubleVector    | vector_of_char_ | Null            |              10 |               4 |         0.34366 |   1254462.00000 |            0.80 | 
DoubleVector    | use_cereal      | Null            |              10 |               4 |         0.04172 |    152291.25000 |            6.57 | 
Complete.

我还对数字到字符串的转换算法进行了基准测试,以比较 std::stringstream、fmt::MemoryWriter 和 std::vector 的性能。

Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
     Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  | 
-----------------------------------------------------------------------------------------------------------------------------------------------
DoubleVectorCon | toStringStream  | Null            |              10 |               4 |         1.00000 |   1272667.00000 |            0.79 | 
FloatVectorConv | toStringStream  | Null            |              10 |               4 |         1.00000 |   1272573.75000 |            0.79 | 
int_conversion  | toStringStream  | Null            |              10 |               4 |         1.00000 |    248709.00000 |            4.02 | 
size_t_conversi | toStringStream  | Null            |              10 |               4 |         1.00000 |    252063.00000 |            3.97 | 
DoubleVectorCon | toMemoryWriter  | Null            |              10 |               4 |         0.98468 |   1253165.50000 |            0.80 | 
DoubleVectorCon | toVectorOfChar  | Null            |              10 |               4 |         0.97146 |   1236340.50000 |            0.81 | 
FloatVectorConv | toMemoryWriter  | Null            |              10 |               4 |         0.98419 |   1252454.25000 |            0.80 | 
FloatVectorConv | toVectorOfChar  | Null            |              10 |               4 |         0.97369 |   1239093.25000 |            0.81 | 
int_conversion  | toMemoryWriter  | Null            |              10 |               4 |         0.11741 |     29200.50000 |           34.25 | 
int_conversion  | toVectorOfChar  | Null            |              10 |               4 |         0.87105 |    216637.00000 |            4.62 | 
size_t_conversi | toMemoryWriter  | Null            |              10 |               4 |         0.13746 |     34649.50000 |           28.86 | 
size_t_conversi | toVectorOfChar  | Null            |              10 |               4 |         0.85345 |    215123.00000 |            4.65 | 
Complete.

从上面的表格我们可以看出:

  1. Edgar Rokyan 解决方案比 stringstream 解决方案慢 10%。使用fmt 库的解决方案对于double、int 和size_t 这三种研究数据类型是最好的。 sprintf + std::vector 解决方案比双数据类型的 fmt 解决方案快 1%。但是,我不推荐将 sprintf 用于生产代码的解决方案,因为它们不优雅(仍然以 C 风格编写)并且不能开箱即用地处理不同的数据类型,例如 int 或 size_t。

  2. 基准测试结果还表明,fmt 是高级整数数据类型序列化,因为它比其他方法至少快 7 倍。

  3. 如果我们使用二进制格式,我们可以将此算法加速 10 倍。这种方法比写入格式化的文本文件要快得多,因为我们只从内存到输出的原始复制。如果您想拥有更灵活和便携的解决方案,请尝试cerealboost::serializationprotocol-buffer。根据this performance study 的说法,麦片似乎是最快的。

【讨论】:

  • 在版本 3 中,如果您使用 vector&lt;char&gt; 来连接所有单独的字符串而不是 std::stringstream,您将获得明显更好的性能。基准问题:stackoverflow.com/q/4340396/103167
  • 非常感谢您的链接。你的示例代码不再存在,所以我不得不猜测你做了什么。我还添加了另一种使用 fmt(github.com/fmtlib/fmt) 的解决方案。从我的基准测试结果来看,std::vector 是最快的解决方案,但是,这个解决方案并不比使用 stringstream 和 fmt::MemoryWriter 的解决方案快得多。
  • @hungptit:缓存到流一般不会产生太大影响,因为std::ofstream在写入文件之前已经缓存了。当然,除非您在每个元素之后使用std::endl,它调用flush,它要求一个系统调用。
  • @MatthieuM。我更新了性能研究,性能结果表明我们可以通过将输出缓存到 std::stringstream 来提高 10%。
  • @BenVoigt 我已经更新了我的解决方案,使用 std::vector 并不比使用 std::stringstream 好很多。
【解决方案3】:

借助迭代器和copy 函数,您还可以使用一种相当简洁的形式将任何vector 的内容输出到文件中。

std::ofstream fout("vector.txt");
fout.precision(10);

std::copy(numbers.begin(), numbers.end(),
    std::ostream_iterator<double>(fout, "\n"));

该解决方案在执行时间方面与 LogicStuff 的解决方案几乎相同。但它也说明了如何仅使用单个 copy 函数打印内容,正如我想的那样,它看起来不错。

【讨论】:

  • 您可能想改用 ostreambuf_iterator。这消除了为每个项目创建蒸汽哨兵对象。
  • 我不知道性能,但这个答案是我见过的最优雅的答案。它教会了我关于std::ostream_iterator 的知识。 +1
  • 终于有人在代码中表达了意图:)。为了通用性:对开始和结束迭代器使用免费函数:copy(begin(numbers), end(numbers), ostream_iterator...).
  • @xtofl 我只是在这里放了一个对 C++ 98/03 有效的解决方案。当然,我们可以用begin/end 函数重写它。但我的主要目的是展示即使没有一些 C++ 11 的东西,STL 是多么的壮丽:)
  • @DanielJour:你确定吗? ostreambuf_iterator 可以流式传输 char 以外的其他内容吗? (cpp.sh/7aea)
【解决方案4】:

好的,我很遗憾有三个解决方案试图给你一条鱼,但没有一个解决方案试图教你如何钓鱼。

当您遇到性能问题时,解决方案是使用分析器,并修复分析器显示的任何问题。

在过去 10 年出货的任何计算机上,将 300,000 个双精度转换为字符串都不需要 3 分钟。

在过去 10 年出货的任何计算机上,将 3 MB 数据写入磁盘(平均大小为 300,000 个双精度数)不会花费 3 分钟。

如果您对此进行分析,我猜您会发现 fout 被刷新 300,000 次,而且刷新速度很慢,因为它可能涉及阻塞或半阻塞 I/O。因此,您需要避免阻塞 I/O。这样做的典型方法是将所有 I/O 准备到单个缓冲区(创建一个字符串流,写入该缓冲区),然后将该缓冲区一次性写入物理文件。这是hungptit描述的解决方案,但我认为缺少的是解释为什么该解决方案是一个好的解决方案。

或者,换一种说法:分析器会告诉你调用 write()(在 Linux 上)或 WriteFile()(在 Windows 上)比仅仅将几个字节复制到内存缓冲区要慢得多,因为这是用户/内核级别的转换。如果 std::endl 导致每个双精度都发生这种情况,那么您的时间会很糟糕(缓慢)。将其替换为仅保留在用户空间中并将数据放入 RAM 中的东西!

如果这仍然不够快,可能是字符串上 operator

【讨论】:

  • 调整缓冲不是最简单的吗?不是很熟悉的 std:: 流,可能他们不支持。
  • @hyde:缓冲在那里。 cout &lt;&lt; endl 实际上会刷新缓冲区。
  • @xtofl 那是行缓冲的,这就是破坏性能的原因,因为它每隔几十个字节或其他什么就刷新一次。完全缓冲意味着,当缓冲区已满时它会刷新,因此只有 1 KB 的缓冲区会减少 10 倍的刷新频率,而 1 MB 的缓冲区会减少 10000 倍的频率。
  • cout &lt;&lt; endl 实际上意思是'冲洗,现在'!所以在这个cout &lt;&lt; value &lt;&lt; endl 中,每次加倍之后都会有一个同花顺。这就是@Jon Watte 所说的。 en.cppreference.com/w/cpp/io/manip/endl。所以是的:“合理的缓冲区大小”是存在的,但它已通过使用 endl 明确关闭。
【解决方案5】:

您的程序中有两个主要瓶颈:输出和格式化文本。

为了提高性能,您需要增加每次调用的数据输出量。例如,500 个字符的 1 次输出传输比 1 个字符的 500 次传输快。

我的建议是将数据格式化为一个大缓冲区,然后块写入缓冲区。

这是一个例子:

char buffer[1024 * 1024];
unsigned int buffer_index = 0;
const unsigned int size = my_vector.size();
for (unsigned int i = 0; i < size; ++i)
{
  signed int characters_formatted = snprintf(&buffer[buffer_index],
                                             (1024 * 1024) - buffer_index,
                                             "%.10f", my_vector[i]);
  if (characters_formatted > 0)
  {
      buffer_index += (unsigned int) characters_formatted;
  }
}
cout.write(&buffer[0], buffer_index);

在弄乱代码之前,您应该首先尝试更改编译器中的优化设置。

【讨论】:

  • io 流缓冲在底层,所以不需要这么多的努力。不幸的是,OP 代码使用了std::endl,它不必要地刷新缓冲区,破坏了大部分好处。
  • 是的,IO 流缓冲区在后台。但是,这种技术允许自定义缓冲区大于 io 流缓冲区。
  • 这个。在某些条件下,std 流可以与 C printf 函数一样快。然而在错误的条件下,它们的速度会慢几个数量级,因此如果需要性能,请使用 C 的 printf 函数。
  • 您必须进行测量和测试,以确保您选择的缓冲区大小比 iostreams 实现选择的缓冲区大小更合适。我还担心 snprintf 格式字符串的解析可能比(可能是内联的)流插入运算符花费更多。
【解决方案6】:

这里有一个稍微不同的解决方案:以二进制形式保存你的双打。

int fd = ::open("/path/to/the/file", O_WRONLY /* whatever permission */);
::write(fd, &vector[0], vector.size() * sizeof(vector[0]));

既然你提到你有 300k 双打,等于 300k * 8 字节 = 2.4M,你可以在不到 0.1 秒内将它们全部保存到本地磁盘文件。这种方法的唯一缺点是保存的文件不像字符串表示那样可读,但是 HexEditor 可以解决这个问题。

如果您更喜欢更健壮的方式,在线上有大量可用的序列化库/工具。它们提供了更多的好处,例如语言中立、机器无关、灵活的压缩算法等。这是我经常使用的两个:

【讨论】:

  • 如果您不想将解决方案限制为 Posix,则可以使用 C++ API 来执行此操作。
  • @AdrianMcCarthy:C++ 只为字符 I/O 提供 API,std::ios::binary 只禁用换行符,它不会禁用字符编码步骤。也许字符编码步骤在某些流行的 C++ 库中是不可操作的……但没有可移植的方法来确保这一点。
  • @Ben Voigt:但任何字符编码步骤都可能是可逆的,因此只需将向量内容的字节写入二进制流并将它们读回即可。 (在标准中哪里可以读到这样的字符编码步骤?我找不到任何暗示二进制流的basic_ostream::write 可以进行任何类型的编码。)
  • @AdrianMcCarthy:它在 27.9.2 [filebuf] 中。特别是,overflow 虚拟覆盖(实际上是从缓冲区写入文件的函数)的描述说“消费字符的行为是通过首先转换来执行的,就像 a_codecvt.out(state, b, p, end, xbuf, xbuf+XSIZE, xbuf_end) 一样”
猜你喜欢
  • 2016-02-05
  • 1970-01-01
  • 1970-01-01
  • 2012-03-15
  • 2012-03-12
  • 1970-01-01
  • 2018-07-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多