使用多个分隔符进行快速字符串拆分

时间:2011-03-31 20:32:24

标签: c++ string boost split performance

我在StackOverflow上调查了一段时间,找到了将多个分隔符的字符串拆分成vector< string >的好算法。我还找到了一些方法:

Boost方式:

boost::split(vector, string, boost::is_any_of(" \t"));

getline方法:

std::stringstream ss(string);
std::string item;
while(std::getline(ss, item, ' ')) {
    vector.push_back(item);
}

Boost的标记化方式:

char_separator<char> sep(" \t");
tokenizer<char_separator<char>> tokens(string, sep);
BOOST_FOREACH(string t, tokens)
{
   vector.push_back(t);
}

和酷STL方式:

     istringstream iss(string);
     copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter<vector<string> >(vector));

和Shadow2531的方法(参见链接主题)。

他们大多来自this topic。但不幸的是他们没有解决我的问题:

  • Boost的分割很容易使用,但是大数据(大多数情况下大约1.5 * 10 ^ 6个单元)和大约10个分隔符我使用它的速度非常慢。

  • getline,STL和Shadow2531的方法存在的问题是我只能使用一个char作为分隔符。我需要更多。

  • Boost的标记化在速度方面更加可怕。用10个分隔符花了11秒钟将一个字符串分成1.5 * 10 ^ 6个元素。

所以我不知道该怎么做:我希望有一个非常快速的字符串拆分算法和多个分隔符。

Boost的分裂是最大值还是有办法更快

3 个答案:

答案 0 :(得分:33)

我想到了两件事:

  1. 使用字符串视图而不是字符串 作为分割结果,节省了很多 分配。
  2. 如果你知道你只是 将与chars一起工作(在 [0,255]范围),尝试使用a bitset来测试成员资格而不是 find进入分隔符 字符。
  3. 以下是应用这些想法的快速尝试:

    #include <vector>
    #include <bitset>
    #include <iostream>
    #include <boost/algorithm/string/split.hpp>
    #include <boost/algorithm/string/classification.hpp>
    #include <boost/timer.hpp>
    
    using namespace std;
    size_t const N = 10000000;
    
    template<typename C>
    void test_custom(string const& s, char const* d, C& ret)
    {
      C output;
    
      bitset<255> delims;
      while( *d )
      {
        unsigned char code = *d++;
        delims[code] = true;
      }
      typedef string::const_iterator iter;
      iter beg;
      bool in_token = false;
      for( string::const_iterator it = s.begin(), end = s.end();
        it != end; ++it )
      {
        if( delims[*it] )
        {
          if( in_token )
          {
            output.push_back(typename C::value_type(beg, it));
            in_token = false;
          }
        }
        else if( !in_token )
        {
          beg = it;
          in_token = true;
        }
      }
      if( in_token )
        output.push_back(typename C::value_type(beg, s.end()));
      output.swap(ret);
    }
    
    template<typename C>
    void test_strpbrk(string const& s, char const* delims, C& ret)
    {
      C output;
    
      char const* p = s.c_str();
      char const* q = strpbrk(p+1, delims);
      for( ; q != NULL; q = strpbrk(p, delims) )
      {
        output.push_back(typename C::value_type(p, q));
        p = q + 1;
      }
    
      output.swap(ret);
    }
    
    template<typename C>
    void test_boost(string const& s, char const* delims)
    {
      C output;
      boost::split(output, s, boost::is_any_of(delims));
    }
    
    int main()
    {
      // Generate random text
      string text(N, ' ');
      for( size_t i = 0; i != N; ++i )
        text[i] = (i % 2 == 0)?('a'+(i/2)%26):((i/2)%2?' ':'\t');
    
      char const* delims = " \t[],-'/\\!\"§$%&=()<>?";
    
      // Output strings
      boost::timer timer;
      test_boost<vector<string> >(text, delims);
      cout << "Time: " << timer.elapsed() << endl;
    
      // Output string views
      typedef string::const_iterator iter;
      typedef boost::iterator_range<iter> string_view;
      timer.restart();
      test_boost<vector<string_view> >(text, delims);
      cout << "Time: " << timer.elapsed() << endl;
    
      // Custom split
      timer.restart();
      vector<string> vs;
      test_custom(text, delims, vs);
      cout << "Time: " << timer.elapsed() << endl;
    
      // Custom split
      timer.restart();
      vector<string_view> vsv;
      test_custom(text, delims, vsv);
      cout << "Time: " << timer.elapsed() << endl;
    
      // Custom split
      timer.restart();
      vector<string> vsp;
      test_strpbrk(text, delims, vsp);
      cout << "Time: " << timer.elapsed() << endl;
    
      // Custom split
      timer.restart();
      vector<string_view> vsvp;
      test_strpbrk(text, delims, vsvp);
      cout << "Time: " << timer.elapsed() << endl;
    
      return 0;
    }
    

    使用GCC 4.5.1使用启用了-O4标志的Boost 1.46.1进行编译,得到:

    • 时间:5.951(Boost.Split + vector)
    • 时间:3.728(Boost.Split +矢量
    • 时间:1.662(自定义拆分+矢量)
    • 时间:0.144(自定义拆分+矢量)
    • 时间:2.13(Strpbrk + vector)
    • 时间:0.527(Strpbrk + vector)

    注意:输出略有不同,因为我的自定义功能会丢弃空标记。但是,如果您决定使用它,您可以根据自己的需要调整此代码。

答案 1 :(得分:2)

要结合Pablo和larsmans答案的最佳部分,请使用(offset, size)对来存储子字符串,使用strcspn来获取每个条目的范围。

答案 2 :(得分:1)

在这么大的字符串上,使用ropes代替它可能会有所回报。或者使用Pablo建议的字符串视图:(char const*size_t)对。如果您有良好的strpbrk实现,则无需bitset技巧。