Guava學(xué)習(xí)之Splitter

本文是對 Guava 中 Splitter 的學(xué)習(xí)介紹弧可。歡迎加入學(xué)習(xí)項目: LearningGuava蛀柴。

使用示例

以下參考:官方文檔吞滞。

Splitter

概述

Java 中關(guān)于分詞的工具類會有一些古怪的行為掰读。例如:String.split 函數(shù)會悄悄地丟棄尾部分割符亡容,而 StringTokenizer 處理5個空格字符串品洛,結(jié)果將會什么都沒有树姨。

問題:",a,,b,".split(",") 的結(jié)果是什么?

  1. "", "a", "", "b", ""
  2. null, "a", null, "b", null
  3. "a", null, "b"
  4. "a", "b"
  5. 以上都不是

正確答案是:5 以上都不是桥状,應(yīng)該是 "", "a", "", "b"帽揪。只有尾隨的空字符串被跳過。這樣的結(jié)果很令人費解辅斟。

Splitter 可以讓你使用一種非常簡單流暢的模式來控制這些令人困惑的行為转晰。

Splitter.on(',')
    .trimResults()
    .omitEmptyStrings()
    .split("foo,bar,,   qux");

以上代碼將會返回 Iterable<String> ,包含 "foo"、 "bar"查邢、 "qux"蔗崎。一個 Splitter 可以通過這些來進(jìn)行劃分:Patternchar扰藕、 String缓苛、CharMatcher

如果你希望返回的是 List 的話邓深,可以使用這樣的代碼 Lists.newArrayList(splitter.split(string))未桥。

工廠函數(shù)

方法 描述 例子
Splitter.on(char) 基于特定字符劃分 Splitter.on(';')
Splitter.on(CharMatcher) 基于某些類別劃分 Splitter.on(';')
Splitter.on(String) 基于字符串劃分 Splitter.on(CharMatcher.BREAKING_WHITESPACE)
Splitter.on(CharMatcher.anyOf(";,."))
Splitter.on(Pattern)
Splitter.onPattern(String)
基于正則表達(dá)式劃分 Splitter.on(", ")
Splitter.fixedLength(int) 按指定長度劃分,最后部分可以小于指定長度但不能為空 Splitter.fixedLength(3)

修改器

方法 描述 例子
omitEmptyStrings() 移去結(jié)果中的空字符串 Splitter.on(',').omitEmptyStrings().split("a,,c,d") 返回 "a", "c", "d"
trimResults() 將結(jié)果中的空格刪除芥备,等價于trimResults(CharMatcher.WHITESPACE) Splitter.on(',').trimResults().split("a, b, c, d") 返回 "a", "b", "c", "d"
trimResults(CharMatcher) 移除匹配字符 Splitter.on(',').trimResults(CharMatcher.is('_')).split("_a ,_b_ ,c__") 返回 "a ", "b_ ", "c"
limit(int) 達(dá)到指定數(shù)目后停止字符串的劃分 Splitter.on(',').limit(3).split("a,b,c,d") 返回 "a", "b", "c,d"

Splitter.MapSplitter

以下參考:Guava 是個風(fēng)火輪之基礎(chǔ)工具(2)冬耿。

通過 SplitterwithKeyValueSeparator 方法可以獲得 Joiner.MapJoiner 對象。

MapSpliter 只有一個公共方法萌壳,如下所示淆党。可以看到返回的對象是 Map<String, String>讶凉。

public Map<String, String> split(CharSequence sequence)

以下代碼將返回這樣的 Map: {"1":"2", "3":"4"}染乌。

Splitter.on("#").withKeyValueSeparator(":").split("1:2#3:4");

需要注意的是,MapSplitter 對鍵值對格式有著嚴(yán)格的校驗懂讯,下例會拋出 java.lang.IllegalArgumentException 異常荷憋。

Splitter.on("#").withKeyValueSeparator(":").split("1:2#3:4:5"); 

因此,如果希望使用 MapSplitter 來拆分 KV 結(jié)構(gòu)的字符串褐望,需要保證鍵-值分隔符和鍵值對之間的分隔符不會稱為鍵或值的一部分勒庄。也許是出于類似方面的考慮,MapSplitter 被加上了 @Beta 注解(未來不保證兼容瘫里,甚至可能會移除)实蔽。所以一般推薦使用 JSON 而不是 MapJoiner + MapSplitter

源碼分析

以下參考:Guava 是個風(fēng)火輪之基礎(chǔ)工具(2)谨读。

Splitter 的實現(xiàn)中有十分明顯的策略模式和模板模式局装,有各種神乎其技的方法覆蓋,還有 Guava 久負(fù)盛名的迭代技巧和惰性計算劳殖。

成員變量

Splitter 類有 4 個成員變量:

  • CharMatcher trimmer:用于描述刪除拆分結(jié)果的前后指定字符的策略铐尚。
  • boolean omitEmptyStrings:用于控制是否刪除拆分結(jié)果中的空字符串。
  • Strategy strategy:用于幫助實現(xiàn)策略模式哆姻。
  • int limit:用于控制拆分的結(jié)果個數(shù)宣增。

策略模式

Splitter 可以根據(jù)字符、字符串矛缨、正則爹脾、長度還有 Guava 自己的字符匹配器 CharMatcher 來拆分字符串帖旨,基本上每種匹配模式的查找方法都不太一樣,但是字符拆分的基本框架又是不變的灵妨,所以策略模式正好合用碉就。

策略接口的定義很簡單,就是傳入一個 Splitter 和一個待拆分的字符串闷串,返回一個迭代器瓮钥。

  private interface Strategy {
    Iterator<String> iterator(Splitter splitter, CharSequence toSplit);
  }

每個工廠函數(shù)創(chuàng)建最后都需要去調(diào)用基本的私有構(gòu)造函數(shù)。這個創(chuàng)建過程中烹吵,主要是提供一個可以創(chuàng)建 Iterator<String>Strategy碉熄。

  private Splitter(Strategy strategy, boolean omitEmptyStrings, CharMatcher trimmer, int limit);

Splitter on(final CharMatcher separatorMatcher) 創(chuàng)建函數(shù)為例肋拔,這里返回的是 SplittingIterator (它是個抽象類锈津,繼承了 AbstractIterator,而 AbstractIterator 繼承了 Iterator)凉蜂。

  public static Splitter on(final CharMatcher separatorMatcher) {
    checkNotNull(separatorMatcher);
    return new Splitter(
        new Strategy() {
          @Override
          public SplittingIterator iterator(Splitter splitter, final CharSequence toSplit) {
            return new SplittingIterator(splitter, toSplit) {
              @Override
              int separatorStart(int start) {
                return separatorMatcher.indexIn(toSplit, start);
              }

              @Override
              int separatorEnd(int separatorPosition) {
                return separatorPosition + 1;
              }
            };
          }
        });
  }

SplittingIterator 需要覆蓋實現(xiàn) separatorStartseparatorEnd 兩個方法才能實例化琼梆。這兩個方法也是 SplittingIterator 用到的模板模式的重要組成。

惰性迭代器與模板模式

惰性計算目的是要最小化計算機(jī)要做的工作窿吩,即把計算推遲到不得不算的時候進(jìn)行茎杂。Java中的惰性計算可以參考《你應(yīng)該更新的 Java 知識之惰性求值:Supplier 和 Guava》

Guava 中的迭代器使用了惰性計算的技巧纫雁,它不是一開始就算好結(jié)果放在列表或集合中煌往,而是在調(diào)用 hasNext 方法判斷迭代是否結(jié)束時才去計算下一個元素。

AbstractIterator

為了看懂 Guava 的惰性迭代器實現(xiàn)轧邪,我們要從 AbstractIterator 開始刽脖。

AbstractIterator 使用私有的枚舉變量 state 來記錄當(dāng)前的迭代進(jìn)度,比如是否找到了下一個元素忌愚,迭代是否結(jié)束等曲管。AbstractIterator 有一個抽象方法 computeNext,負(fù)責(zé)計算下一個元素硕糊。由于 state 是私有變量院水,而迭代是否結(jié)束只有在調(diào)用 computeNext 的過程中才知道,于是提供了一個保護(hù)的 endOfData 方法癌幕,允許子類將 state 設(shè)置為 State.DONE衙耕。

  private enum State {
    READY,
    NOT_READY,
    DONE,
    FAILED,
  }

AbstractIterator 實現(xiàn)了迭代器最重要的兩個方法,hasNextnext勺远。

hasNext 很容易理解,一上來先判斷迭代器當(dāng)前狀態(tài)时鸵,如果已經(jīng)結(jié)束胶逢,就返回 false厅瞎;如果已經(jīng)找到下一個元素,就返回 true初坠,不然就試著找找下一個元素和簸。

  @Override
  public final boolean hasNext() {
    checkState(state != State.FAILED);
    switch (state) {
      case READY:
        return true;
      case DONE:
        return false;
      default:
    }
    return tryToComputeNext();
  }

next 則是先判斷是否還有下一個元素,屬于防御式編程碟刺,先對自己做保護(hù)锁保;然后把狀態(tài)復(fù)原到還沒找到下一個元素,然后返回結(jié)果半沽。至于為什么要把 next 置為 null爽柒,可能是幫助 JVM 回收對象。

   @Override
    public final T next() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }
      state = State.NOT_READY;
      T result = next;
      next = null;
      return result;
    }

tryToComputeNext 可以認(rèn)為是對模板方法 computeNext 的包裝調(diào)用者填,首先把狀態(tài)置為失敗浩村,然后才調(diào)用 computeNext。這樣一來占哟,如果計算下一個元素的過程中發(fā)生 RuntimeException心墅,整個迭代器的狀態(tài)就是 State.FAILED,一旦收到任何調(diào)用都會拋出異常榨乎。

private boolean tryToComputeNext() {
    state = State.FAILED; // 暫時悲觀
    next = computeNext();
    if (state != State.DONE) {
      state = State.READY;
      return true;
    }
    return false;
  }

AbstractIterator 的代碼就這些怎燥,我們現(xiàn)在知道了它的子類需要覆蓋實現(xiàn) computeNext 方法,然后在迭代結(jié)束時調(diào)用 endOfData蜜暑。接下來看看 SplittingIterator 的實現(xiàn)刺覆。

SplittingIterator

SplittingIterator 還是一個抽象類,雖然實現(xiàn)了 computeNext 方法史煎,但是它又定義了兩個虛函數(shù):

  • separatorStart: 返回分隔符在指定下標(biāo)之后第一次出現(xiàn)的下標(biāo)
  • separatorEnd: 返回分隔符在指定下標(biāo)后面第一個不包含分隔符的下標(biāo)谦屑。

之前的策略模式中我們可以看到,這兩個函數(shù)在不同的策略中有各自不同的覆蓋實現(xiàn)篇梭,在 SplittingIterator 中氢橙,這兩個函數(shù)就是模板函數(shù)。

接下來看看 SplittingIterator 的核心函數(shù) computeNext恬偷,這個函數(shù)一直在維護(hù)的兩個內(nèi)部全局變量: offsetlimit悍手。

  @Override
    protected String computeNext() {
      // 返回的字符串介于上一個分隔符和下一個分隔符之間。
      // nextStart 是返回子串的起始位置袍患,offset 是下次開啟尋找分隔符的地方坦康。 
      int nextStart = offset;
      while (offset != -1) {
        int start = nextStart;
        int end;

        // 找 offset 之后第一個分隔符出現(xiàn)的位置
        int separatorPosition = separatorStart(offset);
        if (separatorPosition == -1) {
          // 處理沒找到的情況
          end = toSplit.length();
          offset = -1;
        } else {
          // 處理找到的情況
          end = separatorPosition;
          offset = separatorEnd(separatorPosition);
        }
        
        // 處理的是第一個字符就是分隔符的特殊情況
        if (offset == nextStart) {
          // 發(fā)生情況:空字符串 或者 整個字符串都沒有匹配。
          // offset 需要增加來尋找這個位置之后的分隔符诡延,
          // 但是沒有改變接下來返回字符串的 start 的位置滞欠,
          // 所以此時它們二者相同。
          offset++;
          if (offset > toSplit.length()) {
            offset = -1;
          }
          continue;
        }

        // 根據(jù) trimmer 來對找到的元素做前處理肆良,比如去除空白符之類的筛璧。
        while (start < end && trimmer.matches(toSplit.charAt(start))) {
          start++;
        }
        // 根據(jù) trimmer 來對找到的元素做后處理逸绎,比如去除空白符之類的。
        while (end > start && trimmer.matches(toSplit.charAt(end - 1))) {
          end--;
        }
        // 根據(jù)需要去除那些是空字符串的元素夭谤,trim完之后變成空字符串的也會被去除棺牧。
        if (omitEmptyStrings && start == end) {
          // Don't include the (unused) separator in next split string.
          nextStart = offset;
          continue;
        }

        // 判斷 limit,
        if (limit == 1) {
          // The limit has been reached, return the rest of the string as the
          // final item. This is tested after empty string removal so that
          // empty Strings do not count towards the limit.
          end = toSplit.length();
          // 調(diào)整 end 指針的位置標(biāo)記 offset 為 -1朗儒,下一次再調(diào)用 computeNext 
          // 的時候就發(fā)現(xiàn) offset 已經(jīng)是 -1 了颊乘,然后就返回 endOfData 表示迭代結(jié)束。
          offset = -1;
          // Since we may have changed the end, we need to trim it again.
          while (end > start && trimmer.matches(toSplit.charAt(end - 1))) {
            end--;
          }
        } else {
          // 還沒到 limit 的極限醉锄,就讓 limit 自減
          limit--;
        }

        return toSplit.subSequence(start, end).toString();
      }
      return endOfData();
    }
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乏悄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子榆鼠,更是在濱河造成了極大的恐慌纲爸,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妆够,死亡現(xiàn)場離奇詭異识啦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)神妹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進(jìn)店門颓哮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鸵荠,你說我怎么就攤上這事冕茅。” “怎么了蛹找?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵姨伤,是天一觀的道長。 經(jīng)常有香客問我庸疾,道長乍楚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任届慈,我火速辦了婚禮徒溪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘金顿。我一直安慰自己臊泌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布揍拆。 她就那樣靜靜地躺著渠概,像睡著了一般。 火紅的嫁衣襯著肌膚如雪礁凡。 梳的紋絲不亂的頭發(fā)上高氮,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天慧妄,我揣著相機(jī)與錄音顷牌,去河邊找鬼剪芍。 笑死,一個胖子當(dāng)著我的面吹牛窟蓝,可吹牛的內(nèi)容都是我干的罪裹。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼运挫,長吁一口氣:“原來是場噩夢啊……” “哼状共!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谁帕,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤峡继,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后匈挖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碾牌,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年儡循,在試婚紗的時候發(fā)現(xiàn)自己被綠了舶吗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡择膝,死狀恐怖誓琼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肴捉,我是刑警寧澤腹侣,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站齿穗,受9級特大地震影響傲隶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缤灵,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一伦籍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腮出,春花似錦帖鸦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至馋劈,卻和暖如春攻锰,著一層夾襖步出監(jiān)牢的瞬間晾嘶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工娶吞, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留垒迂,地道東北人。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓妒蛇,卻偏偏與公主長得像机断,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绣夺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 一:官方教程 網(wǎng)址:http://blog.csdn.net/axi295309066/article/detai...
    漫步_2310閱讀 1,350評論 0 1
  • Java8 in action 沒有共享的可變數(shù)據(jù)吏奸,將方法和函數(shù)即代碼傳遞給其他方法的能力就是我們平常所說的函數(shù)式...
    鐵牛很鐵閱讀 1,241評論 1 2
  • 晚上,兒子回來給我說:媽媽陶耍,我們班好多同學(xué)都談朋友了奋蔚。我問他:你有嗎?兒子說:沒人看上我烈钞。兒子有點沮喪泊碑,說:我學(xué)習(xí)...
    微笑的石子媽媽閱讀 135評論 0 0
  • 走著走著 就散了 回憶都淡了; 看著看著 就累了 星光也暗了棵磷; 聽著聽著 就醒了 開始埋怨了蛾狗; 回頭發(fā)現(xiàn) 你不見了...
    我遇見一棵樹閱讀 270評論 3 7
  • 每次都有很多的話想說 可是到了嘴邊就忘的干凈了 感覺活到了高中的那個時候了 很累 不論學(xué)習(xí) 還是 感情 但是沒有那...
    怪了喵的咪閱讀 120評論 0 0