您好,登錄后才能下訂單哦!
1.引言
split方法很常用,記得我入職公司的時候,第一道筆試題就是關于字符串的拆分拼裝等,這也足以說明,大公司對這些方法的重視程度.
其實我們平時一般用到的都是這些方法最簡單的用法,但如果你不了解他的實現原理,碰到某些特殊問題終究是會被卡住的,于是就產生了所謂的bug,而這也就是大神和菜鳥的區別之一吧.廣度是一方面,但真正看一個程序員是不是牛逼,重要的還是看他的深度,比如這個split的用法,如果你還停留在簡單的用法上,不妨看看后面,也看看你的深度,與君共勉!
2.split用法
先上一個例子:
1.最普通的用法
String str1 = "aa,bb";
String[] split1 = str1.split(",");
System.out.println(split1.length);
//這個結果是2,都知道的
2.比較普通的用法
String str2 = "";
String[] split2 = str2.split(",");
System.out.println(split2.length);
//這個結果是1,但部分人會認為這個的結果是0,
//這個為什么是1,我會在后面說
3.看起來比較奇怪的用法
String str3 = ",";
String[] split3 = str3.split(",");
System.out.println(split3.length);
//這個結果是0,但部分人會認為結果是1,部分人會認為結果是2.
//這個又為什么是0,我也會在后面說
3.split源碼分析
split方法準確的來說有兩個參數(String regex, int limit),只不過平時我們用的,是split的一個重載方法(String regex),默認是把第二個參數設置為0,源碼如下:
public String[] split(String regex) {
return split(regex, 0);
}
public String[] split(String regex, int limit) {
具體實現...
}
3.1.參數解釋—regex
1.如果表達式不匹配輸入的任何內容,返回的數組只具有一個元素,即此字符串。(尤其注意空字符串這種情況,他也是一個字符串)
2.可以匹配的情況下,每一個字符串都由另一個匹配給定表達式的子字符串終止,或者由此字符串末尾終止(數組中的字符串按照他們在此字符串出現的順序排列)
3.2.參數解釋—limit
該參數用于控制模式匹配使用的次數,可以影響到數組的長度
1.limit>0:
模式匹配將被最多應用n-1次,數組的長度將不會大于n,數組的最后一項將包含所有超出最后匹配的定界符的輸入。
2.limit<0:
模式匹配將應用盡可能多的次數,而且數組的長度是任何長度。
3.lilmit=0:
模式匹配將被應用盡可能多的次數,數組可以是任何長度,并且結尾空字符串將被丟棄。
3.3.不同limit值的情況下的split結果驗證
假設有字符串aa,bcd,eef,
3.3.1.limit=0,regex=","
尾部的逗號,直接被忽略,頭部的逗號不會忽略
String line = ",aa,bcd,eef,,,";
String[] split = line.split(",",0);
System.out.println(split.length);//4
3.3.2.limit=2,regex=","
總長度被限制成最大2個
String line = ",aa,bcd,eef,,,";
String[] split = line.split(",",2);
System.out.println(split.length);//2
3.3.3.limit=100,regex=","
總長度被限制成最大100個
但結果是7個,說明當limit大于0,并且遠大于應該有的長度時,頭部和尾部的逗號都沒有被忽略
String line = ",aa,bcd,eef,,,";
String[] split = line.split(",",100);
System.out.println(split.length);//7
3.3.4.limit=-1,regex=","
結果是7個,說明當limit小于0時,頭部和尾部的逗號都沒有被忽略
String line = ",aa,bcd,eef,,,";
String[] split = line.split(",",100);
System.out.println(split.length);//7
4.擴展
在java.lang包中有String.split()方法的原型是:
public String[] split(String regex, int limit)
split函數是用于使用特定的切割符(regex)來分隔字符串成一個字符串數組,函數返回是一個數組。在其中每個出現regex的位置都要進行分解。
需要注意是有以下幾點:
(1)regex是可選項。字符串或正則表達式對象,它標識了分隔字符串時使用的是一個還是多個字符。如果忽略該選項,返回包含整個字符串的單一元素數組。
(2)limit也是可選項。該值用來限制返回數組中的元素個數。
(3)要注意轉義字符:“.”和“|”都是轉義字符,必須得加"\"。同理:*和+也是如此的。
如果用“.”作為分隔的話,必須是如下寫法:
String.split("\."),這樣才能正確的分隔開,不能用String.split(".");
如果用“|”作為分隔的話,必須是如下寫法:
String.split("\|"),這樣才能正確的分隔開,不能用String.split("|");
(4)如果在一個字符串中有多個分隔符,可以用“|”作為連字符,比如:“acountId=? and act_id =? or extra=?”,把三個都分隔出來,可以用
String.split("and|or");
(5)split函數結果與regex密切相關,常見的幾種情況如下所示:
public class SplitTest {
public static void main(String[] args) {
String str1 = "a-b";
String str2 = "a-b-";
String str3 = "-a-b";
String str4 = "-a-b-";
String str5 = "a";
String str6 = "-";
String str7 = "--";
String str8 = "";
split(str1);
split(str2);
split(str3);
split(str4);
split(str5);
split(str6);
split(str7);
split(str8);
}
public static void split(String demo){
String[] array = demo.split("-");
int len = array.length;
System.out.print("\"" + demo + "\" 分割后的長度為:" + len);
if(len >= 0)
{
System.out.print(",分割后的結果為:");
for(int i=0; i<len; i++)
{
System.out.print(" \""+array[i]+"\"");
}
}
System.out.println();
}
}
運行結果為:
"a-b" 分割后的長度為:2,分割后的結果為: "a" "b"
"a-b-" 分割后的長度為:2,分割后的結果為: "a" "b"
"-a-b" 分割后的長度為:3,分割后的結果為: "" "a" "b"
"-a-b-" 分割后的長度為:3,分割后的結果為: "" "a" "b"
"a" 分割后的長度為:1,分割后的結果為: "a"
"-" 分割后的長度為:0,分割后的結果為:
"--" 分割后的長度為:0,分割后的結果為:
"" 分割后的長度為:1,分割后的結果為: ""
由此可以得出來:
當字符串只包含分隔符時,返回數組沒有元素;
當字符串不包含分隔符時,返回數組只包含一個元素(該字符串本身);
字符串最尾部出現的分隔符可以看成不存在,不影響字符串的分隔;
字符串最前端出現的分隔符將分隔出一個空字符串以及剩下的部分的正常分隔;
不知道這么做的原因是什么,所以在使用split()中需要注意這些問題,解決方法其實也挺簡單的,變通下即可。
例如:
String splitString = "\|";
String s = "|42345|||";
s = s+"| ";
String info[] = s.split(splitString);
System.out.println(info.length);
for (int i = 0; i < info.length; i++) {
System.out.println(info[i]+" >>>> " + i);
}
谷歌的guava包,也有對split的重寫,返回的是list數組集合.
具體使用如下:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>24.1-jre</version>
</dependency>
String line = ",aa,bcd,eef,,,";
List<String> split2 = Splitter.on(",").splitToList(line);
System.out.println(split2.size());//7
根據結果,我們可以看到XM代理申請www.fx61.com/brokerlist/xm.html,谷歌的split默認是頭部和尾部的逗號都沒有被忽略,相當于java包下split的limit設置為-1
相比下,java包下split的limit默認不寫就是0,即頭部逗號沒有被忽略,而尾部逗號是被忽略的
一定要注意區分
手寫String的split()方法,String的split()方法分三種情況:
regex只有一位,且不為列出的特殊字符;
regex有兩位,第一位位轉義字符且第二位不是數字和字母;
最后一種情況就是正則表達式去拆分字符串。
package com.dalingjia.algorithm.string;
import com.google.common.collect.Lists;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
手動實現java的split()方法 */ public class SpiltUtil { private static final String contant = ".$ |
()[{^?*+\"; public static String[] splitMethod(String string, String regex){ char ch = 0; int off = 0; int next = 0; ArrayList<String> list = Lists.newArrayList(); if(regex.length() == 1 && contant.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) { while ((next = string.indexOf(ch,off)) > 0){ list.add(string.substring(off, next)); off = next + 1; } if(off == 0){ return new String[]{string}; } list.add(string.substring(off, string.length())); return interceptEmpty(list); } return regexSplit(string, regex); } private static String[] regexSplit(String string, String regex) { int off = 0; //將給定的正則表達式編譯到模式中 Pattern pattern = Pattern.compile(regex); //創建給定輸入與此模式匹配的匹配器 Matcher m = pattern.matcher(string); List<String> list = Lists.newArrayList(); while (m.find()){ //m.start(): 返回第一個匹配字符的索引 list.add(string.substring(off, m.start())); //m.end(): 返回最后匹配字符之后的偏移量 off = m.end(); } if(off == 0){ return new String[]{string}; } list.add(string.substring(off, string.length())); return interceptEmpty(list); } //截取空字符串 private static String[] interceptEmpty(List<String> list){ //截取空的字符串 int resultSize = list.size(); while (resultSize>0 && list.get(resultSize-1).length() == 0){ resultSize--; } String[] strings = new String[resultSize]; return list.subList(0, resultSize).toArray(strings);} //測試方法 @Test br/>} //測試方法 @Test //測試regex只有一位,且不為列出的特殊字符 String s1 = "gg,tge,hbfs,ijkd,,,"; String[] strings1 = splitMethod(s1, ","); for (int i = 0; i < strings1.length; i++) { System.out.println(strings1[i]); } //測試regex有兩位,第一位位轉義字符且第二位不是數字和字母 String s2 = "bb\'dn\'ags\'kl\'\'"; String[] strings2 = splitMethod(s2,"\'"); for (int i = 0; i < strings2.length; i++) { System.out.println(strings2[i]); } //測試正則表達式 String ss = "ac32dge533grhr139ljs343"; String[] strings = splitMethod(ss,"[\d]+"); for (int i = 0; i < strings.length; i++) { System.out.println(strings[i]); } } } String的split()方法源碼如下: public String[] split(String regex, int limit) { char ch = 0; if ( ( //如果regex只有一位,且不為列出的特殊字符 (regex.length() == 1 && ".$ |
()[{^?*+\".indexOf(ch = regex.charAt(0)) == -1) |
---|
//如果regex有2位,第一位為轉義字符,且第二位不是數字或字母
/**
* “||”: 如果左邊計算后的操作數為true,右邊則不再執行,返回true;
*
* “|”:前后兩個操作數都會進行計算。也就是說:“|”不存在短路。
*/
(regex.length() == 2 && regex.charAt(0) == '\\' && ( ((ch = regex.charAt(1))-'0')|('9'-ch) ) < 0 && ((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0)
)
&&
/**
* UTF-16 編碼中的 Unicode 高代理項代碼單元的最小值, '\uD800'
* UTF-16 編碼中的 Unicode 低代理項代碼單元的最大值, '\uDFFF'
*/
(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 (off == 0)
return new String[]{this};
// 添加最后一個子序列
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
//截取后面的空字符串
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);
}
//第三種情況,利用正則表達式去split字符串
return Pattern.compile(regex).split(this, limit);
}
String的split()方法最后一行調用的Pattern的split()方法,二則源碼大同小異。
源碼如下:
public String[] split(CharSequence input, int limit) {
int index = 0;
boolean matchLimited = limit > 0;
ArrayList<String> matchList = new ArrayList<>();
Matcher m = matcher(input);
while(m.find()) {
if (!matchLimited || matchList.size() < limit - 1) {
if (index == 0 && index == m.start() && m.start() == m.end()) {
continue;
}
String match = input.subSequence(index, m.start()).toString();
matchList.add(match);
index = m.end();
} else if (matchList.size() == limit - 1) { // last one
String match = input.subSequence(index, input.length()).toString();
matchList.add(match);
index = m.end();
}
}
// 如果沒有匹配的,直接返回該字符串
if (index == 0)
return new String[] {input.toString()};
// 添加最后一個子序列
if (!matchLimited || matchList.size() < limit)
matchList.add(input.subSequence(index, input.length()).toString());
// 截取后面的空字符
int resultSize = matchList.size();
if (limit == 0)
while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
resultSize--;
String[] result = new String[resultSize];
//集合截取
return matchList.subList(0, resultSize).toArray(result);
}
正則表達式的常用方法:
Pattern.compile(String regex): 將給定的正則表達式編譯到模式中;
Pattern.split(CharSequence input):按照此模式拆分給定的輸入序列;
Pattern.matcher(CharSequence input): 創建給定輸入與此模式匹配的匹配器;
Matcher.find(): 查找與該模式匹配的輸入序列的下一個子序列;
Matcher.start(): 返回第一個匹配字符的索引;
Matcher.end(): 返回最后匹配字符之后的偏移量。
注意:若split后字符串數組的尾部字符串為"",則需要舍棄空字符串
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。