String 中 split 方法的效率问题

问:String 中 split 方法使用时有什么效率问题吗?

答:String 的 split 分割字符串函数我们一般会如下方式使用。

String[] arr = "a,b,c".split(",");

上面代码非常简洁, 也没什么问题。不过一旦我们进行如下方式使用就可能会有问题了。

for (String line : lines) {
    line.split("[,.!+@#$%^&*()\\- ]");
}

这种写法虽说看起来没问题,但其实并非如此。实际上,这样写的话一旦遇到调用频率高或是需要分割大文本的情况就会出现内存占用大及运行耗时长的问题。至于为什么会这样,我们可以来看一下该函数是如何实现的:

public String[] split(String regex, int limit) {
    /* fastpath if the regex is a
     (1)one-char String and this character is not one of the
        RegEx's meta characters ".$|()[{^?*+\\", or
     (2)two-char String and the first char is the backslash and
        the second is not the ascii digit or ascii letter.
     */
    char ch = 0;
    if (((regex.value.length == 1 &&
         ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
         (regex.length() == 2 &&
          regex.charAt(0) == '\\' &&
          (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
          ((ch-'a')|('z'-ch)) < 0 &&
          ((ch-'A')|('Z'-ch)) < 0)) &&
        (ch < Character.MIN_HIGH_SURROGATE ||
         ch > Character.MAX_LOW_SURROGATE))
    {
        int off = 0;
        int next = 0;
        boolean limited = limit > 0;
        ArrayList<String> list = new ArrayList<>();
        while ((next = indexOf(ch, off)) != -1) {
            if (!limited || list.size() < limit - 1) {
                list.add(substring(off, next));
                off = next + 1;
            } else {    // last one
                //assert (list.size() == limit - 1);
                list.add(substring(off, value.length));
                off = value.length;
                break;
            }
        }
        // If no match was found, return this
        if (off == 0)
            return new String[]{this};

        // Add remaining segment
        if (!limited || list.size() < limit)
            list.add(substring(off, value.length));

        // Construct result
        int resultSize = list.size();
        if (limit == 0) {
            while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                resultSize--;
            }
        }
        String[] result = new String[resultSize];
        return list.subList(0, resultSize).toArray(result);
    }
    //重点
    return Pattern.compile(regex).split(this, limit);
}

通过上面源码可以看出在大部分情况下 split(String regex) 函数实际上是新建了一个 Pattern 对象,再去调用它的 split(CharSequence input, int limit) 函数,仅有两种情况例外:

  1. 传入的 regex 参数仅有一个字符,且非正则表达式中的 “.$|()[{^?*+” 字符。
  2. 传入的 regex 参数仅有两个字符,且第一个字符为反斜杠,第二个字符不是数字或字母。

这样事情就很明朗了,我们在分词的时候调用了多少次 split 函数就等于新建了多少 Pattern 对象,自然会慢。因此只要对原来的实现稍加改动就能解决这个问题:

Pattern pattern = Pattern.compile("[,.!+@#$%^&*()\\- ]");
for (String line : lines) {
    pattern.split(line);
}

需要注意的是 String 中除了 split 还有一些函数也会在内部生成 Pattern 对象,包括:

  • matches(String regex)
  • replaceFirst(String regex, String replacement)
  • replaceAll(String regex, String replacement)
  • split(String regex, int limit)

所以使用这些函数的时候就要小心了,如果是被反复调用的情况,最好是声明成一个 Pattern 常量,再去调用对应的函数。

上面的内容摘自公众号《码农每日一题》

我的理解:

如果是很常规的用法,只是按照逗号后者是冒号或者是空格之类的,这个是不影响效率的,但若是需要按照很复杂的正则表达式去分的话,就会每次都新建一个Pattern对象,影响效率。所以我们直接新建好对象,调用方法,这样就节省了系统去区分标志是否是复杂正则表达式的过程,省了新建对象的时间,节省了时间。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值