前言

本章介绍字符串分割的使用案例,同时会给出一些使用建议。

版本约定

字符串分割,需要注意两个问题:特殊字符和丢失结尾空字符串,我们一个一个来分析。

特殊字符

字符串分割,一般会使用 String 类的 split 方法,比如我需要按照逗号分割字符串。

  1. public static void main(String[] args) {
  2. String str = "1,2,3,4";
  3. String[] arr = str.split(",");
  4. System.out.println(Arrays.toString(arr));
  5. }

运行程序,输出:

  1. [1, 2, 3, 4]

但是,如果我想通过特殊符号 | 分割字符串呢?

  1. public static void main(String[] args) {
  2. String str = "1|2|3|4";
  3. String[] arr = str.split("|");
  4. System.out.println(Arrays.toString(arr));
  5. }

运行程序,输出:

  1. [1, |, 2, |, 3, |, 4]

悲剧的发现,执行结果并不是我们期望的结果,这是为什呢?该如何处理呢?

我们先来看下 String 类中 split 方法的注释和源码。

  1. /**
  2. * Splits this string around matches of the given <a
  3. * href="../util/regex/Pattern.html#sum">regular expression</a>.
  4. *
  5. * <p> This method works as if by invoking the two-argument {@link
  6. * #split(String, int) split} method with the given expression and a limit
  7. * argument of zero. Trailing empty strings are therefore not included in
  8. * the resulting array.
  9. *
  10. * <p> The string {@code "boo:and:foo"}, for example, yields the following
  11. * results with these expressions:
  12. *
  13. * <blockquote><table cellpadding=1 cellspacing=0 summary="Split examples showin
  14. * <tr>
  15. * <th>Regex</th>
  16. * <th>Result</th>
  17. * </tr>
  18. * <tr><td align=center>:</td>
  19. * <td>{@code { "boo", "and", "foo" }}</td></tr>
  20. * <tr><td align=center>o</td>
  21. * <td>{@code { "b", "", ":and:f" }}</td></tr>
  22. * </table></blockquote>
  23. *
  24. *
  25. * @param regex
  26. * the delimiting regular expression
  27. *
  28. * @return the array of strings computed by splitting this string
  29. * around matches of the given regular expression
  30. *
  31. * @throws PatternSyntaxException
  32. * if the regular expression's syntax is invalid
  33. *
  34. * @see java.util.regex.Pattern
  35. *
  36. * @since 1.4
  37. * @spec JSR-51
  38. */
  39. public String[] split(String regex) {
  40. return split(regex, 0);
  41. }

传入 split 方法的分割参数其实代表的是一个正则表达式,它是通过正则表达式来实现字符串分割的。

正则表达式中有些字符具有特殊的含义,如果在匹配中要用到它本来的含义,需要进行转义(在其前面加一个 \),具体哪些字符需要转移,会另外写一篇文章描述。

不得已,我们需要先对分隔符中的正则表达式特殊字符进行转义,才能传入 split 方法。

  1. public static void main(String[] args) {
  2. String str = "1|2|3|4";
  3. String[] arr = str.split("\\|");
  4. System.out.println(Arrays.toString(arr));
  5. }

运行程序,输出:

  1. [1, 2, 3, 4]

这样才能得出正确的结果。

上面讲字符串比较的时候,最后使用了 StringUtils 工具类的中的方法,那字符串分割,它有提供相应的方法么?
image.png
答案是肯定的,貌似重载的方法还挺多,功能丰富,先使用一下,看看效果。

  1. public static void main(String[] args) {
  2. String str = "1|2|3|4";
  3. String[] arr = StringUtils.split(str, "|");
  4. System.out.println(Arrays.toString(arr));
  5. }

运行程序,输出:

  1. [1, 2, 3, 4]

直接结果正确,而且我也没考虑特殊字符的情况,因为 StringUtils 的 split 方法不是通过正则表达式的方式实现字符串分割的,所以我们也不用考虑正则表达式的特殊字符转义的问题了。

同样的,关于字符串分割也是推荐使用 StringUtils 类中使用的 split 方法。

丢失结尾空字符串

先猜一下如下代码的执行结果是多少,是不是和你预期的一样?

  1. public static void main(String[] args) {
  2. String str = "1||3|";
  3. String[] arr = str.split("\\|");
  4. System.out.println(arr.length);
  5. }

运行程序,输出:

  1. 3

(⊙o⊙)…,根据字符 | 分割的话,我想上面应该能分成 4 断,那执行结果应该 4,为什么这里输出了 3 呢?

查看 String 类的 split 方法,发现它还调用了另外一个重载方法。

  1. public String[] split(String regex) {
  2. return split(regex, 0);
  3. }
  4. public String[] split(String regex, int limit) {
  5. ......
  6. }

limit 参数表示什么含义呢?根据注释描述,我们知道 limit 参数用来控制模式应用的次数,因此它会影响所得数组的长度。

  • limit 大于 0:则模式将被最多应用 n-1 次,数组的长度将不会大于 n,而且数组的最后一项将包含所有超出最后匹配的定界符的输入。
  • limit 等于 0:则模式将被应用尽可能多的次数,数组可以是任何长度,并且结尾空字符串将被丢弃。
  • limit 小于 0:则模式将被应用尽可能多的次数,数组可以是任何长度。

split(String regex) 方法默认传的 limit 值是 0,那就是说执行结果会丢弃结尾的空字符串,刚好上面的例子中的字符串根据字符 | 分割后,结尾就是一个空字符串,所以执行结果是 3。

根据上面的描述,我们如果想保留结尾的空字符串的话,需要调用 split(String regex, int limit) 方法。

  1. public static void main(String[] args) {
  2. String str = "1||3|";
  3. String[] arr = str.split("\\|", -1);
  4. System.out.println(arr.length);
  5. }

运行程序,输出:

  1. 4

这样才能达到预期的结果。

我们使用 StringUtils 工具类中的 split 方法试试呢。

  1. public static void main(String[] args) {
  2. String str = "1||3|";
  3. String[] arr = StringUtils.split(str, "|");
  4. System.out.println(arr.length);
  5. }

运行程序,输出:

  1. 2

这个更狠,把空字符串都丢掉了,所以我们平时在写代码的时候,还是需要写 Unit Test 的,需要把可能的场景列出来,通过 Unit Test 覆盖测试一遍,才能知道我们的代码是否健壮,能否达到预期的结果。

StringUtils 工具类中 split 相关的源码比较多,这里就不一一列出来了,大家直接进到该类中,看注释就能找到你需要执行效果的方法。

比如这里我想包含空字符串,不想过滤空字符串的话,可以使用 StringUtils 类中的 splitByWholeSeparatorPreserveAllTokens 方法。

  1. public static void main(String[] args) {
  2. String str = "1||3|";
  3. String[] arr = StringUtils.splitByWholeSeparatorPreserveAllTokens(str, "|");
  4. System.out.println(arr.length);
  5. }

运行程序,输出:

  1. 4

总结

字符串的分割推荐使用 StringUtils 工具类中的 split 方法,如果不想丢失空字符串,推荐使用 StringUtils 工具类中的 splitByWholeSeparatorPreserveAllTokens 方法。

该工具类中有很多 split 的重载方法,在使用的时候根据自己的需要选择。

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/yrq1di 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。