Java實現(xiàn)字符串的分割(基于String.split()方法)
前言
本章對Java如何實現(xiàn)字符串的分割,是基于jDK1.8版本中的String.split()方法。
本文篇幅較長,內(nèi)容較為復(fù)雜涉及到許多小細(xì)節(jié),都是我在使用時候以及查閱資料時候遇到的坑,建議反復(fù)觀看??!
內(nèi)容中含有對源碼的解讀,如果可以建議詳細(xì)讀懂源碼,有助于對split的理解使用。
最后,長文警告,可按需觀看?。?/p>
一、JDK-1.8-API文檔說明(推薦閱讀)
首先對java-JDK-1.8的文檔進(jìn)行解讀,以下是我從文檔中截取的兩張圖片,分別是關(guān)于split單參數(shù)方法與split雙參數(shù)方法,如下圖:
對以上內(nèi)容提煉重點:
- 該方法是將字符串分割的方法,通過給定的String regex作為分割符分割。分割后生成一個字符串?dāng)?shù)組,該數(shù)組中子字符串的排序為他們在原字符串中的順序。
- 如果沒有找到對應(yīng)的分隔符(regex)則返回一個長度為1的字符串?dāng)?shù)組,僅存放原字符串
- 對于單個參數(shù)的方法有:該方法是調(diào)用了限制參數(shù)(limit)為0的雙參數(shù)split方法
- 對于雙參數(shù)的方法有:limit是控制模式應(yīng)用的次數(shù)因此限定了輸出字符串?dāng)?shù)組的長度,并給出三種分類:
1)limit>0:模式最多應(yīng)用n-1次,數(shù)組的長度不大于n,數(shù)組的最后一個條目將包含超出匹配分隔符的所有輸入
2)limit<0:模式將被應(yīng)用到盡可能多的次數(shù),且數(shù)組可以有任何長度
3)limit=0:模式將被應(yīng)用到盡可能多的次數(shù),且數(shù)組可以有任何長度,并且尾隨的空字符串將被丟棄
二、簡單的使用
了解完jdk文檔提供的基礎(chǔ)使用方法,接下來進(jìn)行以下簡單的一個對于split方法的入門使用,首先是對于單個字符作為分隔符的使用以及對于使用正則表達(dá)式分割
1、單個字符分隔
/** * 輸出分隔后的字符數(shù)組 * 為了可以明顯的看出空字符串的輸出,如遇空字符串則輸出為——“空字符串” * @param split */ private void printSplit(String[] split) { for (String temp : split) { //空字符串的話輸出--“空字符串” if (temp.equals("")) { System.out.println("空字符串"); } else { System.out.println(temp); } } } /** * 基礎(chǔ)使用1:單個字符-: */ @Test public void Test1() { string = "boo:and:foo"; String[] split = string.split(":"); printSplit(split); } /** * 基礎(chǔ)使用1:單個字符-o */ @Test public void Test2() { string = "boo:and:foo"; String[] split = string.split("o"); printSplit(split); }
Test1運行結(jié)果:
Test2運行結(jié)果:
通過單個字符的分割可以看出,基本使用還是比較簡單的,但是在第二個分割字符“o”時產(chǎn)生了一定的問題,就是分割到重復(fù)的字符“o”會在中間出現(xiàn)一個空字符串,以及尾部的空字符串居然并沒有被分割進(jìn)去
2、正則表達(dá)式
/** * 基礎(chǔ)使用2:正則表達(dá)式-1 */ @Test public void Test3() { string = "asd-sdf+sda+sda"; //匹配-或者+ String[] split = string.split("[-\\+]"); printSplit(split); } /** * 基礎(chǔ)使用2:正則表達(dá)式-2 */ @Test public void Test4() { string = "boo1:a2nd:fo3o"; //匹配正整數(shù) String[] split = string.split("[0-9]*[1-9][0-9]*"); printSplit(split); }
Test3運行結(jié)果:
Test4運行結(jié)果:
對于正則表達(dá)式的分割成功了,證明split中參數(shù)String regex是可以支持輸入正則表達(dá)式進(jìn)行分割的
三、Java源碼分析
以下源碼比較繞,建議是跟著下面的測試代碼一邊調(diào)試一邊理解(ps:源碼英文已轉(zhuǎn)譯)。
比較難的說明文字后面都有以下都會有一小部分的總結(jié),如果實在看不懂看總結(jié)也可以~
/** * 單個參數(shù)的方法其實就是調(diào)用了雙參數(shù)的方法,第二個參數(shù)limit為0 */ public String[] split(String regex) { return split(regex, 0); } public String[] split(String regex, int limit) { /*英文轉(zhuǎn)譯: 如果正則表達(dá)式是 (1)一個字符的字符串,并且這個字符不是 正則表達(dá)式的元字符".$|()[{^?* + \ \”,或 (2)兩個字符的字符串,第一個字符是反斜杠和 第二個不是ascii數(shù)字或ascii字母。*/ //下面有對于if判斷的拆解,因為篇幅占位大,放到本段代碼末尾,建議先看 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)) //以上這一大片都是if判斷條件↑ { //定義上一個字符串分割結(jié)束的位置,起始為0 int off = 0; //定義下一個分隔符在待分割字符串中的位置,,起始為0 int next = 0; //boolean值,如果limit大于0為true ,小于等于0皆為false boolean limited = limit > 0; //定義分割后的字符串?dāng)?shù)組,因為String[]長度固定,不便于使用 ArrayList<String> list = new ArrayList<>(); /** * while判斷語句中做了兩件事: * 1)使用indexof查詢off以后下一個ch的位置,并賦值給next * 2)如果后續(xù)再找不到ch則退出循環(huán) * *if語句中也不簡單,首先看【 !limited 】 *上面limited賦值中可知,如果limit大于0,則【!limited】恒為false * 如果limit不大于于0,則【!limited】恒為true *翻譯為“人話”就是: * 如果limit大于0則后續(xù)條件才需要判斷,否則if條件一直都是true * 進(jìn)一步推論,如果limit不大于0,則else里面的語句塊肯定不會執(zhí)行 * *其次看看第二個條件,【list.size() < limit - 1】:list的長度小于limit-1 *我們可以做出兩種假設(shè): * 1)limit無限大,則這第二個條件一定恒為true * 2)limit很小,那么只有這種情況才會出現(xiàn)list長度會小于limit的情況,也就是false * 那么limit的分界線在哪呢?也就是limit的取值如何才會出現(xiàn)有false的情況 * 決定性因素肯定就是 原字符串分割出多少子字符串: * 若limit是大于能分割出的子字符串,表達(dá)式一定為true * 若limit是小于能分割出的子字符串個數(shù),那表達(dá)式【list.size()=limit-1】則為false,并且進(jìn)入else語句塊。 * *將兩個條件整合到一起:也就是只有當(dāng)limit>0并且小于能分割出子字符串個數(shù)時 *if才會出現(xiàn)false的情況,并且這時【list.size()=limit-1】,進(jìn)入else語句塊 * *if{...}else{...}里面的語句塊就比較簡單了 * 通過substring進(jìn)行分割字符串,并放入list中 * 然后將off往后移動 * else內(nèi)的語句塊也是一樣的,不過添加的是最后一個子字符串 */ 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 //如果沒有找到匹配項,則返回this //off如果為0,那么就證明上述那個循環(huán)中并未找到匹配項 if (off == 0) return new String[]{this}; // Add remaining segment //添加剩余的部分 //同上,當(dāng)limit不大于0的時候恒為true //只有l(wèi)imit>0而且list長度大于等于limit才為false //因為上面循環(huán)中l(wèi)ist.size()=limit-1,進(jìn)入else語句塊,語句塊中會再給list加入一個元素 //可知,這個if判斷與上面else語句塊兩個互補(bǔ),兩個不會同時運行到 //這個與else語句塊作用一致,都是將最后一個子字符串添加入list if (!limited || list.size() < limit) list.add(substring(off, value.length)); // Construct result //構(gòu)建結(jié)果 //如果limit為0,進(jìn)行特殊處理 //首先字符串?dāng)?shù)組長度大于0并且獲取最后一個字符數(shù)組的字符串長度為0 //簡而言之,前提條件字符數(shù)組長度得大于0(小于0還分割個啥) //其次尋找最后一個是否是空字符串,如果是,將長度減一,如果不是則退出循環(huán) int resultSize = list.size(); if (limit == 0) { while (resultSize > 0 && list.get(resultSize - 1).length() == 0) { resultSize--; } } //定義字符數(shù)組,將list轉(zhuǎn)為String[] //因為后面空字符長度被去掉了,于是空字符被省略了 String[] result = new String[resultSize]; return list.subList(0, resultSize).toArray(result); } //如果不符合if的條件就進(jìn)入這個方法 return Pattern.compile(regex).split(this, limit); } //if條件的拆分 /*如果正則表達(dá)式是 (1)一個字符的字符串,并且這個字符不是 正則表達(dá)式的元字符".$|()[{^?* + \ \”,或 (2)兩個字符的字符串,第一個字符是反斜杠和 第二個不是ascii數(shù)字或ascii字母。*/ ( ( ( regex.value.length == 1 && ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1 //小細(xì)節(jié),這里將ch賦值了,也就是將改字符賦值給了ch ) || ( regex.length() == 2 && regex.charAt(0) == '\\' && (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 //小細(xì)節(jié)*2 && ((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0 ) ) && (ch < Character.MIN_HIGH_SURROGATE ||ch > Character.MAX_LOW_SURROGATE) )
1、源代碼的測試代碼
建議進(jìn)入調(diào)試模式配合上面代碼同步運行,有利于對代碼的解讀
/** * 讀源碼:測試1,單個非特殊字符 * 結(jié)果為并未調(diào)用Pattern.compile * if判斷條件結(jié)果為true */ @Test public void Test5() { string = "boo:and:foo"; String[] split = string.split(":"); printSplit(split); } /** * 讀源碼:測試1,兩個字符,并且第一個字符為\第二個字符不為數(shù)字或者單詞 * 結(jié)果為并未調(diào)用Pattern.compile * if判斷條件結(jié)果為true * 這里需要注意,雖然regex是為\\$但是其實在Java解讀中第一個為轉(zhuǎn)義字符 * 所以傳到方法中其實代碼解讀為\$ * 你們可以在調(diào)試的時候看一下這個入?yún)⒌闹当隳馨l(fā)現(xiàn) */ @Test public void Test6() { string = "boo$and$foo"; String[] split = string.split("\\$"); printSplit(split); } /** * 讀源碼:測試3,三個字符 * if判斷條件結(jié)果為false * 結(jié)果為調(diào)用了Pattern.compile */ @Test public void Test7() { string = "boo:and:foo"; String[] split = string.split("and"); printSplit(split); }
2、源代碼運行原理圖示
下圖為以":"作為分隔符的運行圖示
3、解讀完代碼后的總結(jié)(推薦閱讀)
1.if可以進(jìn)入的條件為單個字符并且不為正則表達(dá)式元字符,或者雙字符,第一個為反斜杠并且第二個字符不為數(shù)字與字母,如此一來,其實第二個條件就是允許輸入正則表達(dá)式元字符,其實整個if條件就是如果是單個字符就可以允許輸入,但是為了遵循正則表達(dá)式的規(guī)則才設(shè)置了兩個字符的條件。
結(jié)論:
String.split()這個方法對于單個字符(包括特殊字符,但是需要轉(zhuǎn)義)是自己進(jìn)行分割的,但是如果是**多個字符,這個方法就會去調(diào)用Pattern.compile(regex).split(this, limit);**這個方法
如果需要多次使用split方法并且都是多個字符作為分隔符,直接使用Pattern.compile(regex).split(this, limit);或許會帶來更高的效率
2.內(nèi)部采用substring()進(jìn)行字符串的分割,然后傳入list集合內(nèi)部,于是如果待分割字符串中分隔符連續(xù)出現(xiàn)就會出現(xiàn)分割出空字符串,詳情可見上面使用“o”進(jìn)行分割出現(xiàn)了一個空字符串,會出現(xiàn)substring(n,n) 這種情況結(jié)果為空字符串
3.如果使用limit =0 的雙參數(shù)方法,區(qū)別于limit <0,split會在生成結(jié)果前檢查后端的空字符串并將其去掉,這就是為什么limit = 0的時候后面的空字符串會被丟棄
四、limit參數(shù)使用區(qū)別
1、limit=0
那么模式將被應(yīng)用盡可能多的次數(shù),數(shù)組可以是任何長度,并且結(jié)尾空字符串將被丟棄。
就是會首先運行出全部分割出的子字符串然后再將后面結(jié)尾的空格去掉
/** * limit參數(shù)區(qū)別:=0 * 輸出全部結(jié)果,去除結(jié)果后面全部空字符數(shù)組 */ @Test public void Test8() { string = "boo:and:foo:::"; String[] split = string.split(":", 0); printSplit(split); }
Test8運行結(jié)果:
2、limit<0
模式將被應(yīng)用盡可能多的次數(shù),而且數(shù)組可以是任何長度。
分割出全部子字符串包含有全部分割結(jié)果
/** * limit參數(shù)區(qū)別:<0 * 輸出全部結(jié)果 */ @Test public void Test9() { string = "boo:and:foo:::"; String[] split = string.split(":", -1); printSplit(split); }
Test9運行結(jié)果:
3、limit>0
模式將被最多應(yīng)用 n - 1 次,數(shù)組的長度將不會大于 n,而且數(shù)組的最后一項將包含所有超出最后匹配的定界符的輸入。
分割出的字符串長度只會小于等于limit,當(dāng)limit小于能分割出的子字符串?dāng)?shù)量時,這個時候數(shù)組長度等于limit
如果limit大于能分割出的子字符串?dāng)?shù)量時,數(shù)組長度等于子字符串?dāng)?shù)量,小于limit
/** * limit參數(shù)區(qū)別:>0 --小于分割出的字符數(shù)組長度 */ @Test public void Test10() { string = "boo:and:foo"; String[] split = string.split(":", 2); printSplit(split); } /** * limit參數(shù)區(qū)別:>0 --大于分割出的字符數(shù)組長度 */ @Test public void Test11() { string = "boo:and:foo"; String[] split = string.split(":", 5); printSplit(split); }
Test10運行結(jié)果:
Test11運行結(jié)果:
五、易錯點(推薦閱讀)
1、分割到第一個字符
當(dāng)?shù)谝粋€字符被分割到,則字符數(shù)組首個字符串為空
原因分析:在源碼中可以看出,源碼使用indexof進(jìn)行查找下一個分隔符的位置,當(dāng)找到分隔符為第一個的時候就會將next賦值為0,然后使用substring分割,于是兩個參數(shù)就變成了subtring(0,0)必然分割出一個空字符串出來
如果開頭的這個空字符串并非想要的理想輸出,只能自己手動去除
/** * 易錯點:分割到第一個字符 */ @Test public void Test12() { string = "boo$and$foo"; String[] split = string.split("b", 0); printSplit(split); }
Test12運行結(jié)果:
2、轉(zhuǎn)義字符\
java中使用\必須再次進(jìn)行一次轉(zhuǎn)義,例如用“\\”代表“\”,并且正則表達(dá)式元字符都必須轉(zhuǎn)義才能作為分隔符
原因分析:split這個方法其實可以看出還是推薦我們使用正則表達(dá)式進(jìn)行分割的,在寫String regex這個參數(shù)我建議還是看著正則表達(dá)式的書寫方法進(jìn)行書寫的
源碼中明確給出說明,正則表達(dá)式元字符前面都需要使用\轉(zhuǎn)義——.$|()[{^?*+\
其次,java中\(zhòng)的使用也必須進(jìn)行轉(zhuǎn)義,在Java中雙反斜杠表示一個反斜杠,書寫中應(yīng)該特別注意
推薦書寫方法:
先找個正則表達(dá)式驗證的網(wǎng)站驗證正則表達(dá)式的書寫,然后復(fù)制進(jìn)去java代碼中,需要注意的是,在java 1.7之后將帶有\(zhòng)的字符串粘貼到雙引號中會自動再添加一個\
/** * 易錯點:轉(zhuǎn)義字符\ */ @Test public void Test13() { //因為java代碼不能直接輸入一個反斜杠,必須進(jìn)行轉(zhuǎn)義,這里的\\表達(dá)為\ string = "boo\\and\\foo"; //這里\\\\應(yīng)該拆開看成為\\ \\,前面兩個代表一個\后面兩個代表一個\ //實際\\\\表達(dá)的含義應(yīng)該為\\,對應(yīng)正則表達(dá)式的語法\\表達(dá)為\ //所以在Java代碼中\(zhòng)\\\在最終處理時候其實表達(dá)為\ String[] split = string.split("\\\\", 0); printSplit(split); } /** * 易錯點:轉(zhuǎn)義字符\ */ @Test public void Test14() { string = "boo+and-foo*boo"; //這里的+-*都是正則表達(dá)式的元字符,都需要使用\轉(zhuǎn)義,然后在Java中再對\轉(zhuǎn)義 //原正則表達(dá)式[\+\-\*] String[] split = string.split("[\\+\\-\\*]", 0); printSplit(split); }
Test13運行結(jié)果:
Test14運行結(jié)果:
3、正則表達(dá)式修飾符不可用
基于運行測試發(fā)現(xiàn)正則表達(dá)式的修飾符在split中使用是無效的,使用的時候注意避開
/** * 易錯點:正則表達(dá)式修飾符不可用 * 理想輸出[(boo:),(nd:foo)] */ @Test public void Test15() { string = "boo:and:foo"; String[] split = string.split("/[a]/g", 0); printSplit(split); }
Test15運行結(jié)果:
總結(jié)
到此這篇關(guān)于Java實現(xiàn)字符串的分割的文章就介紹到這了,更多相關(guān)Java字符串分割內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot Scheduling定時任務(wù)的示例代碼
springBoot提供了定時任務(wù)的支持,通過注解簡單快捷,對于日常定時任務(wù)可以使用。本文詳細(xì)的介紹一下使用,感興趣的可以了解一下2021-08-08SpringBoot返回統(tǒng)一的JSON標(biāo)準(zhǔn)格式實現(xiàn)步驟
這篇文章主要介紹了SpringBoot返回統(tǒng)一的JSON標(biāo)準(zhǔn)格式,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08深入學(xué)習(xí)java中的Groovy 和 Scala 類
本文將探討三種下一代 JVM 語言:Groovy、Scala 和 Clojure,比較并對比新的功能和范例,讓 Java 開發(fā)人員對自己近期的未來發(fā)展有大體的認(rèn)識。,需要的朋友可以參考下2019-06-06Java的幾個重要版本_動力節(jié)點Java學(xué)院整理
jdk8 將在2014年3月份發(fā)布,迄今為止,可能是最大更新的java版本,也是令人期待的一個版本,在Java中引入閉包概念對Java程序開發(fā)方法的影響甚至?xí)笥贘ava5中引入的泛型特征對編程方式帶來的影響2017-06-06Java實現(xiàn)簡易學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實現(xiàn)簡易學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07OpenFeign在傳遞參數(shù)為對象類型是為空的問題
這篇文章主要介紹了OpenFeign在傳遞參數(shù)為對象類型是為空的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03