String.split()方法你可能不知道的一面


一、問題

    java中String的split()是我們經常使用的方法,用來按照特定字符分割字符串,那么我們看以下一段代碼:

    public void splitTest() {
        String str = "aaa|bbb|ccc";
        String[] array = str.split("|");
        System.out.println(Arrays.toString(array));
    }

    是不是感覺很簡單,就是吧str按照"|"分割,結果就是[aaa,bbb,ccc]嘛。如果你這么想,那么以后在用這個方法時你可能會犯下大錯,把程序跑起來,你會驚訝的發現程序輸入如下結果:

[, a, a, a, |, b, b, b, |, c, c, c]

    為什么會出現這種情況呢?我們再來運行一下str.split("");就會發現結果和之前的結果一樣,也就是說我們使用"|"分割的時候其實split是按照空字符分割的。

二、探究

    為了找到原因,我們在來使用其他幾種字符測試一下,結果如下

0:[aaa, bbb, ccc]
$:[aaa$bbb$ccc]
,:[aaa, bbb, ccc]
*:異常java.util.regex.PatternSyntaxException: Dangling meta character '*' near index 0
^:[aaa^bbb^ccc]

    我們會發現對於數字,","結果是正確的,而對於特殊字符,結果都不正確,而且如果經驗豐富,你可能已經發現,這些特殊字符都是正則表達式中的匹配符,而那個異常也很明確的說明了這一點。

    因此,我們可以猜測在split內部使用了正則表達式來匹配並分割字符串。那么現在又有一個疑問:我們知道一般情況下String涉及的字符串處理多數使用indexOf()(可能是考慮效率問題吧),那么這里為什么使用正則呢?我們還是直接看看源碼吧:

public String[] split(String regex, int limit) {  
        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);  
    }  

    可以看到方法內有一個if,如果條件為true,那么就使用indexOf()判斷后substring()截取,如果為false,則使用正則處理。那么我們就來分析下這個if的條件:

//第一步部分:當regex的長度為1且不是“.$|()[{^?*+\\”中的時,為真
(regex.value.length == 1 &&".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1)

//第二部分:當長度為2時且第一個字符為“\”轉義字符,第二個字符不是字符0-9 a-z A-Z 以及utf-16之間的字符
(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)

    從if可以看出如果regex內容為一個非正則匹配符或者是轉以后的特殊字符時,采用indexOf()+substring()處理,否則使用正則表達式。
    那么為什么這么做呢,直接使用第一種方法不就行了?其實我們可以考慮一種復雜的情況:

 aaax111xbbbx222xcccx333xddd

    如果我想分割出這樣的結果 [aaa, bbb, ccc, ddd] 應該則么做呢?按照第一種方法,實現起來很麻煩,需要一大堆判斷,反而不如正則方便,而String中的split()方法正是出於這樣的考慮實現的,不信你用split("x.*?x")試試。

三、啟示

    這次這件事雖小,但是卻讓我收獲不少。        

  • 1、使用一個不了解的方法前,一定要看一眼提示,split方法說明里雖然沒有明確的提示上述問題,但是多個地方都提到了正則,連參數名字都是regex,作為一個經驗豐富的程序員,應該想到這一點。
  • 2、對於一些小的功能點(往往我們還胸有成竹)一定要寫個Test測一下,其實把代碼貼過去跑一下費不了多少勁,但是當他淹沒在成百上千行的代碼中時,想要在發現問題就很費勁了,特別是你不調試根本想不到這點會錯。
  • 3、我們在多數項目中都要建個util工具包,封裝各種輔助的操作類,然而這些類的功能往往只是滿足當時的需求,之后用到類似但有變動的功能時往往習慣單獨實現一個,而不是實現一個完善的覆蓋原來的,從設計的角度會造成接口不一致:明明同樣的需求、同樣的參數,我卻還要看看他的內部實現才能選擇用哪個。對於這些工具類,我們不一定要非常完美,但是應該用心做到盡量完善,這也方便復用。

    扯這么多,也不知道說了些什么,OVER,睡覺......







免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM