1. 程式人生 > >String 中 split 方法的效率問題

String 中 split 方法的效率問題

問:String 中 split 方法使用時有什麼效率問題嗎?

答:String 的 split 分割字串函式我們一般會如下方式使用。

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

上面程式碼非常簡潔, 也沒什麼問題。不過一旦我們進行如下方式使用就可能會有問題了。

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

這種寫法雖說看起來沒問題,但其實並非如此。實際上,這樣寫的話一旦遇到呼叫頻率高或是需要分割大文字的情況就會出現記憶體佔用大及執行耗時長的問題。至於為什麼會這樣,我們可以來看一下該函式是如何實現的:

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("[,[email protected]#$%^&*()\\- ]");
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物件,影響效率。所以我們直接新建好物件,呼叫方法,這樣就節省了系統去區分標誌是否是複雜正則表示式的過程,省了新建物件的時間,節省了時間。