1. 程式人生 > >Chromium base庫分割字串SplitString

Chromium base庫分割字串SplitString

前一段時間在工作過程中遇到一個場景需要將http response中的request header中的cookie欄位取出並進行解析,但是手頭沒有解析cookie的工具類,同時cookie的表現就是個字串,於是想到手動分割
但是在C++的標準庫中,並沒有提供類似split的函式,在有些時候可能會很不方便,今天就看看google大佬是如何實現字串的分割的。
在chromium的base庫中有提供和字串相關的函式,在base/strings/string_split.h和base/strings/string_split.cc中定義了SplitString函式用於分割std::string型別的字串,下圖是大體流程圖

std::vector<std::string> SplitString(StringPiece input,
                                     StringPiece separators,
                                     WhitespaceHandling whitespace,
                                     SplitResult result_type) {
  if (separators.size() == 1) {
    return SplitStringT<std::string, std::string, char>(
        input, separators[0], whitespace, result_type);
  }
  return SplitStringT<std::string, std::string, StringPiece>(
      input, separators, whitespace, result_type);
}

其中StringPiece是google定義的一種字串型別,是對std::string的一種封裝,這裡就不再多說,可以直接看成std::string
函式這裡傳入的四個引數分別是輸入字串,分割符,遇到空格處理(保留,跳過),結果型別(保留空值,不保留)
可以看到google根據傳入的分割符的長度做了兩種處理方式,說是做了兩種處理方式,但其實就是講分割符一個看成單個字元char,一個看成std::string字串而已,這與SplitStringT模板的具體實現有關。下面重頭戲來了,看下SplitStringT是如何實現的

template<typename Str, typename OutputStringType, typename DelimiterType>
static std::vector<OutputStringType> SplitStringT(
    BasicStringPiece<Str> str,
    DelimiterType delimiter,
    WhitespaceHandling whitespace,
    SplitResult result_type) {
  std::vector<OutputStringType> result;
  if (str.empty())
    return result;

  size_t start = 0;
  while (start != Str::npos) {
    size_t end = FindFirstOf(str, delimiter, start);

    BasicStringPiece<Str> piece;
    if (end == Str::npos) {
      piece = str.substr(start);
      start = Str::npos;
    } else {
      piece = str.substr(start, end - start);
      start = end + 1;
    }

    if (whitespace == TRIM_WHITESPACE)
      piece = TrimString(piece, WhitespaceForType<Str>(), TRIM_ALL);

    if (result_type == SPLIT_WANT_ALL || !piece.empty())
      result.push_back(PieceToOutputType<Str, OutputStringType>(piece));
  }
  return result;
}

模板引數的三個定義分別是<傳入被分割字串的型別,輸出vector的模板型別,分界符型別>,函式的四個引數和上面說的相同,其中第三個第四個引數主要是對空值和空格的取捨。
Str::npos指的是size_t的最大值,也就是說在這個函式中為了避免字串過長導致函式內部使用的startend發生溢位。
最基本就是迴圈遍歷原始字串,主要使用到了FindFirstOf函式,函式FindFirstOf的實現如下(只看分界符是單個字元char的情況)

size_t FindFirstOf(StringPiece piece, char c, size_t pos) {
  return piece.find(c, pos);
}

這裡的find函式和std::stringfind函式功能一致,從當前pos位置開始想後查詢c字元,找到第一個並返回其所在位置。如果找到了就更新end的值,然後取startend之間的子字串,更新start的值。如果找到下一個分界符了,這個函式返回result就結束了。

剩下最後的部分就是對空格和空值的取捨,下面是取捨部分的程式碼,取自SplitStringT函式

if (whitespace == TRIM_WHITESPACE)
      piece = TrimString(piece, WhitespaceForType<Str>(), TRIM_ALL);

    if (result_type == SPLIT_WANT_ALL || !piece.empty())
      result.push_back(PieceToOutputType<Str, OutputStringType>(piece));

空值取捨就是一個判斷,這裡不再描述,就只看空格取捨,WhitespaceForType<Str>()主要是提供一個模板空格,根據傳入的Str型別不同空格也有可能不同,而TRIM_ALL的主要作用如下

enum TrimPositions {
  TRIM_NONE     = 0,
  TRIM_LEADING  = 1 << 0,
  TRIM_TRAILING = 1 << 1,
  TRIM_ALL      = TRIM_LEADING | TRIM_TRAILING,
};

用於區分空格型別,頭部空格和尾部空格。預設是TRIM_ALL全部。下面是TrimString函式的實現

StringPiece TrimString(StringPiece input,
                       StringPiece trim_chars,
                       TrimPositions positions) {
  return TrimStringPieceT(input, trim_chars, positions);
}
template<typename Str>
BasicStringPiece<Str> TrimStringPieceT(BasicStringPiece<Str> input,
                                       BasicStringPiece<Str> trim_chars,
                                       TrimPositions positions) {
  size_t begin = (positions & TRIM_LEADING) ?
      input.find_first_not_of(trim_chars) : 0;
  size_t end = (positions & TRIM_TRAILING) ?
      input.find_last_not_of(trim_chars) + 1 : input.size();
  return input.substr(begin, end - begin);
}

用的函式也和std::string的成員函式功能一致,是很簡單的去除空格的方式。

附錄

我略微整理了一下一個VS可直接編譯執行的版本(幾乎沒啥改動就是了)

#include <iostream>
#include <vector>
#include <string>

enum TrimPositions {
    TRIM_NONE = 0,
    TRIM_LEADING = 1 << 0,
    TRIM_TRAILING = 1 << 1,
    TRIM_ALL = TRIM_LEADING | TRIM_TRAILING,
};

size_t FindFirstOf(std::string piece, std::string c, size_t pos) {
    return piece.find(c, pos);
}

std::string TrimString(std::string input,
    TrimPositions positions) {
    size_t begin = (positions & TRIM_LEADING) ?
        input.find_first_not_of(" ") : 0;
    size_t end = (positions & TRIM_TRAILING) ?
        input.find_last_not_of(" ") + 1 : input.size();
    return input.substr(begin, end - begin);
}

std::vector<std::string> SplitString(std::string str,
    std::string c,
    bool skip_whitespace,
    bool skip_empty) 
{
    std::vector<std::string> result;
    if (str.empty())
        return result;

    size_t start = 0;
    while (start != std::string::npos) {
        size_t end = FindFirstOf(str, c, start);

        std::string piece;
        if (end == std::string::npos) {
            piece = str.substr(start);
            start = std::string::npos;
        }
        else {
            piece = str.substr(start, end - start);
            start = end + 1;
        }

        if (skip_whitespace)
            piece = TrimString(piece, TRIM_NONE);

        if (!skip_empty || !piece.empty())
            result.push_back(piece);
    }
    return result;
}


int main()
{
    std::vector<std::string> result = SplitString("url=https://www.baidu.com", ".", true, false);
    return 0;
}