Guava-Splitter

Splitter 的实现中有十分明显的策略模式和模板模式,各种的的方法覆盖,还有 Guava 中的迭代技巧和惰性计算。

成员变量
Splitter 类中4 个成员变量分别所代表的含义:

CharMatcher trimmer:用于描述删除拆分结果的前后指定字符的策略。
boolean omitEmptyStrings:用于控制是否删除拆分结果中的空字符串。
Strategy strategy:用于帮助实现策略模式。
int limit:用于控制拆分的结果个数。
Splitter中的策略模式
Splitter 可以根据字符、字符串、正则、长度还有 Guava 自己的字符匹配器 CharMatcher 来拆分字符串,基本上每种匹配模式的查找方法都不太一样,但是字符拆分的基本框架又是不变的,所以策略模式正好合用。

对于Splitter 策略接口的定义很简单,传入的是一个 Splitter 和一个待拆分的字符串,返回一个迭代器。

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

惰性迭代器与模板模式
惰性计算目的是最小化计算机要做的工作,即把计算推迟到不得不算的时候进行。Guava 中的迭代器使用了惰性计算的技巧,它不是一开始就算好结果放在列表或集合中,而是在调用 hasNext 方法判断迭代是否结束时才去计算下一个元素。

AbstractIterator
以Splitter on(final CharMatcher separatorMatcher)为例

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继承了AbstractIterator,而AbstractIterator是Iterator的实现类,AbstractIterator 使用私有的枚举变量 state 来记录当前的迭代进度,比如是否找到了下一个元素,迭代是否结束等。AbstractIterator 有一个抽象方法 computeNext,负责计算下一个元素。由于 state 是私有变量,而迭代是否结束只有在调用 computeNext 的过程中才知道,于是提供了一个保护的 endOfData 方法,允许子类将 state 设置为 State.DONE。

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

AbstractIterator 实现了迭代器最重要的两个方法,hasNext 和 next。

hasNext 一上来先判断迭代器当前状态,如果已经结束,就返回 false;如果已经找到下一个元素,就返回 true,不然就试着找找下一个元素。

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

next 则是先判断是否还有下一个元素,属于防御式编程,先对自己做保护;然后把状态复原到还没找到下一个元素,然后返回结果。至于把 next 置为 null,可能是帮助 JVM 回收对象。

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

tryToComputeNext 可以认为是对模板方法 computeNext 的包装调用,首先把状态置为失败,然后才调用 computeNext。这样一来,如果计算下一个元素的过程中发生 RuntimeException,整个迭代器的状态就是 State.FAILED,一旦收到任何调用都会抛出异常。

private boolean tryToComputeNext() {
//保持悲观状态,就是为了保证computeNext()调用出现异常也不影响后面的逻辑
state = State.FAILED;
next = computeNext();
if (state != State.DONE) {
state = State.READY;
return true;
}
return false;
}

AbstractIterator 代码就这些,因为SplittingIterator实现了他的方法,所以要追溯到SplittingIterator。

SplittingIterator
SplittingIterator 还是一个抽象类,虽然实现了 computeNext 方法,但是它又定义了两个虚函数

separatorStart: 返回分隔符在指定下标之后第一次出现的下标
separatorEnd: 返回分隔符在指定下标后面第一个不包含分隔符的下标。
在上面策略模式中可以看到,这两个函数在不同的策略中有各自不同的覆盖实现,在 SplittingIterator 中,这两个函数就是模板函数。

SplittingIterator中核心的函数还是computeNext,这个函数一直在维护的两个内部全局变量:offset和limit。

@Override protected String computeNext() {
  // 返回的字符串介于上一个分隔符和下一个分隔符之间。
  // nextStart 是返回子串的起始位置,offset 是下次开启寻找分隔符的地方。
  int nextStart = offset;
  while (offset != -1) {
    int start = nextStart;
    int end;
    // 找 offset 之后第一个分隔符出现的位置
    int separatorPosition = separatorStart(offset);
    if (separatorPosition == -1) {
      // 处理没找到的情况
      end = toSplit.length();
      offset = -1;
    } else {
      // 处理找到的情况
      end = separatorPosition;
      offset = separatorEnd(separatorPosition);
    }

    // 处理的是第一个字符就是分隔符的特殊情况
    if (offset == nextStart) {
      // 发生情况:空字符串 或者 整个字符串都没有匹配。
      // offset 需要增加来寻找这个位置之后的分隔符,
      // 但是没有改变接下来返回字符串的 start 的位置,
      // 所以此时它们二者相同。
      offset++;
      if (offset >= toSplit.length()) {
        offset = -1;
      }
      continue;
    }
      // 根据 trimmer 来对找到的元素做前处理,比如去除空白符之类的。
    while (start < end && trimmer.matches(toSplit.charAt(start))) {
      start++;
    }

      // 根据 trimmer 来对找到的元素做后处理,比如去除空白符之类的。
    while (end > start && trimmer.matches(toSplit.charAt(end - 1))) {
      end--;
    }
     // 根据需要去除那些是空字符串的元素,trim完之后变成空字符串的也会被去除。
    if (omitEmptyStrings && start == end) {
      nextStart = offset;
      continue;
    }
      // 判断 limit,
    if (limit == 1) {
      end = toSplit.length();

      // 调整 end 指针的位置标记 offset 为 -1,下一次再调用 computeNext 
      // 的时候就发现 offset 已经是 -1 了,然后就返回 endOfData 表示迭代结束。
      offset = -1;
      while (end > start && trimmer.matches(toSplit.charAt(end - 1))) {
        end--;
      }
    } else 
     // 还没到 limit 的极限,就让 limit 自减{
      limit--;
    }

    return toSplit.subSequence(start, end).toString();
  }
  return endOfData();
}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值