【问题标题】:C++ - How to use a stream to parse a file?C++ - 如何使用流来解析文件?
【发布时间】:2011-06-01 19:01:21
【问题描述】:

我有一个文件,我需要循环分配一个 int foo、字符串类型、64/128 位长的文件。我将如何使用流将这些行解析为以下变量 - 我想坚持流语法( ifs >> foo >> type )但在这种情况下 type 最终将成为 0/ 之后的其余行52 ...那时我只需要一个 char* 并使用 strtoull 等等,所以为什么首先使用流...我希望可读的代码在 char 字符串 / strtok / strtoull 上没有可怕的性能

//input file:
0ULL'04001C0180000000000000000EE317BC'
52L'04001C0180000000'
//ouput:
//0 ULL 0x04001C0180000000 0x000000000EE317BC
//52 L 0x04001C0180000000

  ifstream ifs("input.data");
  int foo;
  string type;
  unsigned long long ull[2];

【问题讨论】:

    标签: c++ iostream


    【解决方案1】:

    Boost Spirit 实现

    这是基于 Boost Spirit (Qi) 的强制实施。为了更好地衡量,包括使用 Boost Spirit (Karma) 进行格式化:

    #include <string>
    #include <iostream>
    #include <fstream>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/karma.hpp>
    
    namespace karma=boost::spirit::karma;
    namespace qi   =boost::spirit::qi;
    
    static qi::uint_parser<unsigned long long, 16, 16, 16> hex16_p; // parse long hex
    static karma::uint_generator<unsigned long long, 16>   hex16_f; // format long hex
    
    int main(int argc, char** args)
    {
        std::ifstream ifs("input.data");
        std::string line;
        while (std::getline(ifs, line))
        {
            std::string::iterator begin = line.begin(), end = line.end();
    
            int                             f0;
            std::string                     f1;
            std::vector<unsigned long long> f2;
    
            bool ok = parse(begin, end,
                    qi::int_                    // an integer
                    >> *qi::alpha               // alternatively: *(qi::char_ - '\'')
                    >> '\'' >> +hex16_p >> '\'' // accepts 'n x 16' hex digits
                , f0, f1, f2);
    
            if (ok)
                std::cout << "Parsed: " << karma::format(
                     karma::int_ 
                     << ' ' << karma::string 
                     << ' ' << ("0x" << hex16_f) % ' '
                 , f0, f1, f2) << std::endl;
            else
                std::cerr << "Parse failed: " << line << std::endl;
        }
    
        return 0;
    }
    

    试运行:

    Parsed: 0 ULL 0x4001c0180000000 0xee317bc
    Parsed: 52 L 0x4001c0180000000
    

    请参阅下面的调整和示例,了解如何调整的信息,例如十六进制输出

    基准测试

    我在您提供的样本输入的 100,000 倍上对 @Cubbi's version 和上述书面进行了基准测试。这最初给 Cubbi 的版本带来了一点优势:0.786s0.823s

    现在,这当然是不公平的比较,因为我的代码每次都在动态构建解析器。像这样从循环中取出:

    typedef std::string::iterator It;
    
    const static qi::rule<It> parser = qi::int_ >> *qi::alpha >> '\'' >> +hex16_p >> '\'';
    bool ok = parse(begin, end, parser, f0, f1, f2);
    

    Boost Spirit 凭借0.093s 赢得了明显的胜利;已经快了 8.5 倍,即使每次迭代仍在构建 karma 格式化程序。

    在两个版本中都注释掉了输出格式,Boost Spirit 的速度提高了 11 倍以上

    调整、示例

    请注意如何轻松调整:

    //  >> '\'' >> +hex16_p >> '\'' // accepts 'n x 16' hex digits
        >> '\'' >> qi::repeat(1,2)[ hex16_p ] >> '\'' // accept 16 or 32 digits
    

    或者像输入一样格式化十六进制输出:

    // ("0x" << hex16_f) % ' '
    karma::right_align(16, '0')[ karma::upper [ hex16_f ] ] % ""
    

    改变样本输出:

    0ULL'04001C0180000000000000000EE317BC'
    Parsed: 0 ULL 04001C0180000000000000000EE317BC
    52L'04001C0180000000'
    Parsed: 52 L 04001C0180000000
    

    HTH

    【讨论】:

    【解决方案2】:

    对于更复杂的解析器(例如boost.spirit)来说,这是一项相当微不足道的任务。

    要使用只需您需要的标准 C++ 流来解决此问题

    • a) 将 ' 视为空格并
    • b) 对字符串“04001C0180000000000000000EE317BC”进行额外的传递,该字符串的值之间没有分隔符。

    借用杰瑞·科芬的sample facet code

    #include <iostream>
    #include <fstream>
    #include <locale>
    #include <vector>
    #include <sstream>
    #include <iomanip>
    struct tick_is_space : std::ctype<char> {
        tick_is_space() : std::ctype<char>(get_table()) {}
        static std::ctype_base::mask const* get_table()
        {
            static std::vector<std::ctype_base::mask>
                   rc(table_size, std::ctype_base::mask());
            rc['\n'] = std::ctype_base::space;
            rc['\''] = std::ctype_base::space;
            return &rc[0];
        }
    };
    
    int main()
    {
        std::ifstream ifs("input.data");
        ifs.imbue(std::locale(std::locale(), new tick_is_space()));
        int foo;
        std::string type, ullstr;
        while( ifs >> foo >> type >> ullstr)
        {
            std::vector<unsigned long long> ull;
            while(ullstr.size() >= 16) // sizeof(unsigned long long)*2
            {
                std::istringstream is(ullstr.substr(0, 16));
                unsigned long long tmp;
                is >> std::hex >> tmp;
                ull.push_back(tmp);
                ullstr.erase(0, 16);
            }
            std::cout << std::dec << foo << " " << type << " "
                      << std::hex << std::showbase;
            for(size_t p=0; p<ull.size(); ++p)
                std::cout << std::setw(16) << std::setfill('0') << ull[p] << ' ';
            std::cout << '\n';
        }
    }
    

    测试:https://ideone.com/lRBTq

    【讨论】:

    • 作为练习我写了the Spirit version;我还对性能差异进行了基准测试,这很有趣(我确实not基准编译时间:^])
    • 我这样做比我正在查看的 15 年前的 strtok 代码要好得多。
    猜你喜欢
    • 2013-09-30
    • 2010-10-25
    • 1970-01-01
    • 2012-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多