问:String 中 split 方法使用时有什么效率问题吗?
答:String 的 split 分割字符串函数我们一般会如下方式使用:
String[] arr = "a,b,c".split(",");
上面代码非常简洁, 也没什么问题。不过一旦我们进行如下方式使用就可能会有问题了:
for (String line : lines) {
line.split("[,.!+@#$%^&*()\\- ]");
}
这种写法虽说看起来没问题,但其实并非如此。实际上,这样写的话一旦遇到调用频率高或是需要分割大文本的情况就会出现内存占用大及运行耗时长的问题。至于为什么会这样,我们可以来看一下该函数是如何实现的:
public String[] split(String regex) {
return split(regex, 0);
}
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)
函数,仅有两种情况例外:
传入的 regex 参数仅有一个字符,且非正则表达式中的
".$|()[{^?*+\"
字符。传入的 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 常量,再去调用对应的函数。
本文引用自 String 中一不留神就中招的效率坑解析