从源码分析:Java中的split()方法
从字符串中出现多次空格时使用’split()'说起
我们一般在使用java中的字符串类String
中的split()
时,比如希望用空格将其隔开时,往往会默认每次只有一个空格出现,那么若出现多个空格,会发生什么呢?
这里,我们可以做一个简单的测试:
public class JavaSplitTest {
public static void main(String[] args) {
String s = "a b";
String[] strs = s.split(" ");
System.out.println(strs.length);
}
}
输出:
4
在上面的这段测试代码中,在单词a与b之间,有3个空格,如果我们认为这些会被当作一个空格看待的话,输出的数组的长度应该是2才对,那么,这个字符串数组strs
中,究竟有哪些元素呢?
我们可以在程序上设置断点来通过Debug查看:
可以看到,数组strs
中除了我们期望存在的a与b之外,其之间还有着两个空元素。
所以,我们猜测,是每两个空格之间的元素都会被储存下来么?
为了印证我们的猜测,可以将a与b之间的空格的数量扩大为10个,那么,此时,结果的数组的长度应该变为11。
接下来,我们通过实验来看一下结果:
public class JavaSplitTest {
public static void main(String[] args) {
String s = "a b";
String[] strs = s.split(" ");
System.out.println(strs.length);
}
}
结果:
11
这样的结果印证了我们前面的猜测。如果只是使用的话,那么看到这里应该就可以帮助读者在接下来的编程中避免一些失误了。
但是,如果想要详细研究一下的话,我们可以从源码来进行分析。
源码分析
我们接下来就可以来看String
中的split()
的源码了:
首先看一下我们刚才用的方法split(String regex)
:
public String[] split(String regex) {
return split(regex, 0);
}
指向了另一个多参数的split()
:
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);
}
这里代码可能比较长,我们可以看一下其中的这一段:
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;
}
}
也就是最开始紧跟着最开始的很长的一个判断语句后面的一段,其中的ch的值来自于判断语句中的ch = regex.charAt(0)
(只要split的参数中不包含".$|()[{^?*+\",那么ch
变量就是在这里赋值)。
其中,off
为要找的子字符串的起始位置,next
为其结束位置,即分隔符所在的位置。list
用来储存子字符串。
而子字符串向list
中添加的方式为list.add(substring(off, next));
,也就是说,每次找到一个分隔符的时候,就会向list
中添加一次,而substring
函数中,若起始位置与终止位置为相邻的话,只会添加一个""
,也就是我们在开始的实验中所看到的空的元素了。