将字符串拆分为列表

为了分割字符串,Guava 引入了 Splitter 类。

为什么不使用 Java 的分裂功能?

通常,Guava 不会复制 Java 中可用的功能。为什么我们需要额外的 Splitter 类?Java 的 String 类中的拆分方法不能为我们提供我们需要的所有字符串拆分机制吗?

回答这个问题的最简单方法是举几个例子。首先,我们将处理以下枪支组合:

String gunslingers = "Wyatt Earp+Doc Holliday";

为了尝试分裂这位传奇的律师和他的牙医朋友,我们可能会尝试以下方法:

String[] result = gunslingers.split("+"); // wrong

但是,在运行时,我们遇到以下异常:

Exception in thread "main" java.util.regex.PatternSyntaxException:
Dangling meta character '+' near index 0

在一个非自愿的 facepalm 之后,我们很快就会记住 String 的 split 方法将正则表达式作为参数,并且+字符用作正则表达式中的量词。然后解决方案是转义+字符,或将其包含在字符类中。

String[] result = gunslingers.split("\\+");
String[] result = gunslingers.split("[+]");

在成功解决了这个问题之后,我们转向了三个火枪手。

String musketeers = ",Porthos , Athos ,Aramis,";

逗号在正则表达式中没有特殊含义,所以让我们通过应用 String.split() 方法并获得结果数组的长度来计算火枪手。

System.out.println(musketeers.split(",").length);

这会在控制台中产生以下结果:

4

四?鉴于字符串包含一个前导和一个尾随逗号,五个结果将属于正常期望范围,但是四个?事实证明,Java 的 split 方法的行为是保留前导,但是丢弃尾随的空字符串,所以数组的实际内容是 ["", "Porthos ", " Athos ", "Aramis"]

因为我们不需要任何空字符串,前导或尾随,所以让我们用循环过滤它们:

for (String musketeer : musketeers.split(",")) {
    if (!musketeer.isEmpty()) {
        System.out.println(musketeer);
    }
}

这给了我们以下输出:

Porthos 
 Athos
Aramis

正如你在上面的输出中所看到的,逗号分隔符之前和之后的额外空格已保留在输出中。为了解决这个问题,我们可以删除不需要的空格,最终会产生所需的输出:

for (String musketeer : musketeers.split(",")) {
    if(!musketeer.isEmpty()) {
        System.out.println(musketeer.trim());
    }
}

(或者,我们也可以调整正则表达式以包含逗号分隔符周围的空格。但是,请记住,在第一个条目之前的前导空格或最后一个条目之后的尾随空格仍然会被保留。)

通过阅读上面的例子,我们不得不得出结论,用 Java 分割字符串充其量是令人讨厌的。

与番石榴分裂的字符串

演示 Guava 如何将弦乐分成相对无痛的体验的最好方法是再次对待相同的两个弦乐,但这次使用的是 Guava 的 Splitter 类。

List<String> gunslingers = Splitter.on('+')
        .splitToList("Wyatt Earp+Doc Holliday");
List<String> musketeers = Splitter.on(",")
        .omitEmptyStrings()
        .trimResults()
        .splitToList(",Porthos , Athos ,Aramis,");

正如你在上面的代码中看到的,Splitter 公开了一个流畅的 API,并允许你通过一系列静态工厂方法创建实例:

  • static Splitter on(char separator)
    允许你将分隔符指定为字符。

  • static Splitter on(String separator)
    允许你将分隔符指定为字符串。

  • static Splitter on(CharMatcher separatorMatcher)
    允许你将分隔符指定为 Guava CharMatcher

  • static Splitter on(Pattern separatorPattern)
    允许你将分隔符指定为 Java 正则表达式 Pattern

  • static Splitter onPattern(String separatorPattern)
    允许你将分隔符指定为正则表达式字符串。

除了这些基于分隔符的工厂方法之外,还有一个 static Splitter fixedLength(int length) 方法来创建 Splitter 实例,该实例将字符串拆分为指定长度的块。

创建 Splitter 实例后,可以应用许多修饰符:

  • Splitter omitEmptyStrings()
    指示 Splitter 从结果中排除空字符串。

  • Splitter trimResults()
    指示 Splitter 使用预定义的空格 CharMatcher 修剪结果。

  • Splitter trimResults(CharMatcher trimmer)
    指示 Splitter 使用指定的 CharMatcher 修剪结果。

在创建(并且可选地修改)Splitter 之后,可以通过调用其 split 方法在字符序列上调用它,该方法将返回 Iterable<String> 类型的对象或其 splitToList 方法,该方法将返回 List<String> 类型的(不可变)对象。

你可能想知道在哪种情况下使用 split 方法(返回 Iterable)而不是 splitToList 方法(返回更常用的 List 类型)将是有益的。简单的答案是:你可能只想使用 split 方法处理非常大的字符串。稍微长一点的答案是因为 split 方法返回 Iterable,可以懒惰地评估拆分操作(在迭代时),从而无需将拆分操作的整个结果保留在内存中。