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后字符串数组的尾部字符串为"",则需要舍弃空字符串
|