// 以下七个参数是在三个参数的构造方法中设置的
// 当前位置
private int currentPosition;
// 下一个要处理的字符的索引
private int newPosition;
// 最大位置,即被分割字符串的长度
private int maxPosition;
// 要分割的字符串
private String str;
// 分隔符,字符串中的每个字符都是一个分隔符
private String delimiters;
// 指示是否将分隔符作为令牌返回的标志。
private boolean retDelims;
// 分隔符是否改变的表示,true表示分隔符发生改变,false表示分隔符没发生改变,默认为false
private boolean delimsChanged;
// 以下三个参数是在构造方法的setMaxDelimCodePoint()方法中设置
// 保存分隔符中ASCII码值最大的的字符
private int maxDelimCodePoint;
// 是否包含代理,即是否涉及到UTF-16和Unicode编码,一般不会涉及到。
private boolean hasSurrogates = false;
// 如果涉及到代理(即涉及到UTF-16和Unicode编码),会将所有的分隔符转换成码点值保存到int数组中
private int[] delimiterCodePoints;
1、StringTokenizer(String str)只有一个入参的构造函数。
// 单个参数的构造函数,参数为需要被解析的字符串
public StringTokenizer(String str) {
// 调用三个参数的构造方法:
// 1、需要被解析的字符串;
// 2、Java默认的分隔符,包括空格、制表符、换行符、回车符、\f;
// 3、指示是否将分隔符作为令牌返回的标志。false表示分隔符本身不会被视为令牌
this(str, " \t\n\r\f", false);
}
2、StringTokenizer(String str, String delim)两个入参的构造函数
// 两个参数的构造方法,str:需要被解析的字符串 delim:分隔符字符串,每一个字符都是一个分隔符
public StringTokenizer(String str, String delim) {
// 1、需要被解析的字符串;
// 2、使用指定的字符作为分割符
// 3、指示是否将分隔符作为令牌返回的标志。false表示分隔符本身不会被视为令牌
this(str, delim, false);
}
3、StringTokenizer(String str, String delim, boolean returnDelims)三个入参的构造函数
public StringTokenizer(String str, String delim, boolean returnDelims) {
// 当前位置设为0,即索引下标为0
currentPosition = 0;
// 下个要处理的位置
newPosition = -1;
// 分隔符是否改变的表示,true表示分隔符发生改变,false表示分隔符没发生改变,默认为false
delimsChanged = false;
// 要分割的字符串
this.str = str;
// 最大位置,即被分割字符串的长度
maxPosition = str.length();
// 设置分隔符,字符串中的每个字符都是一个分隔符
delimiters = delim;
// 指示是否将分隔符作为令牌返回的标志。
retDelims = returnDelims;
// 设置分割符中ASCII码值最大字符
setMaxDelimCodePoint();
}
注意:该方法为public修饰的方法。
1、返回true,表示后续调用nextToken()将成功返回一个token(即分割好的字符串)。
2、返回false,表示后续调用nextToken()将会抛出异常:NoSuchElementException
// 如果retDelims为false(不将分隔符作为令牌返回的标志),判断当前位置及当前位置之后是否还有非分隔符的字符
// 如果retDelims为true(将分隔符作为令牌返回的标志),判断当前位置是否小于被分割字符串的长度
public boolean hasMoreTokens() {
/*
* Temporarily store this position and use it in the following
* nextToken() method only if the delimiters haven't been changed in
* that nextToken() invocation.
*
* 临时存储此位置,并仅在nextToken()调用中分隔符未更改时在以下nextToken()方法中使用它。
*/
// 如果retDelims为false(不将分隔符作为令牌返回的标志),则返回当前位置或当前位置之后第一个非分隔符的字符索引
// 如果retDelims为true(将分隔符作为令牌返回的标志),返回当前位置字符的索引
newPosition = skipDelimiters(currentPosition);
// 当前位置小于被分割字符串的长度
return (newPosition < maxPosition);
}
注意:该方法为public关键字修饰的方法
// 获取下一个根据分隔符拆分的字符串
public String nextToken() {
/*
* If next position already computed in hasMoreElements() and
* delimiters have changed between the computation and this invocation,
* then use the computed value.
* 如果下一个位置已经在hasMoreElements()中计算过,并且分隔符在计算和调用之间发生了变化,则使用计算值。
*/
// 当下一个要处理的字符索引 >= 0 并且 分隔符未发生改变时,将下一个要处理的字符索引 赋值给 当前位置(当前要处理的字符索引)
// 否则重新计算下一个要处理的字符索引
currentPosition = (newPosition >= 0 && !delimsChanged) ?
newPosition : skipDelimiters(currentPosition);
/* Reset these anyway */
// 将分隔符是否改变的标志设置为false,表示分隔符不再变化
delimsChanged = false;
// 重新将下一个要处理的字符索引设置为-1
newPosition = -1;
// 如果当前位置 >= 最大位置,抛出异常
if (currentPosition >= maxPosition)
throw new NoSuchElementException();
// 将当前位置设置为截取字符串的开始下标
int start = currentPosition;
// 获取下一个分隔符的索引。并且currentPosition(当前位置)变为分隔符所在位置的索引
currentPosition = scanToken(currentPosition);
// 截取字符串,从第一个非分隔符的索引开始,到下一个分隔符的索引结束。
return str.substring(start, currentPosition);
}
注意:该方法为public修饰的方法
// 根据新的分隔符获取下一个被拆分出来的字符串
public String nextToken(String delim) {
// 设置分隔符,字符串中的每个字符都是一个分隔符
delimiters = delim;
/* delimiter string specified, so set the appropriate flag.
* 创建对象后重新设置分隔符字符串,需要将此标志设置为true。 */
delimsChanged = true;
// 需改分隔符字符串后需要重新设置设置maxDelimCodePoint(分隔符中的最高字符,即ASCII码值最大的的字符)
setMaxDelimCodePoint();
// 根据分隔符获取下一个拆分出来的字符串
return nextToken();
}
注意:该方法为public修饰的方法,随这nextToken()方法的调用,该方法返回值会发生变化。
// 获取被分割的字符串中还能拆分出几个字符串。随这nextToken()方法的调用,该方法返回值会发生变化
public int countTokens() {
int count = 0;
// currentPosition为成员变量,所以会随着其他方法的调用发生变化,因此该方法的返回值会发生变化
int currpos = currentPosition;
// 当前位置字符 < 被分割字符串长度
while (currpos < maxPosition) {
// 获取当前位置或当前位置之后第一个非分隔符的字符索引
currpos = skipDelimiters(currpos);
// 如果 当前位置 >= 被分割字符串长度 则跳出循环
if (currpos >= maxPosition)
break;
// 获取下一个分隔符的索引
currpos = scanToken(currpos);
count++;
}
return count;
}
注意:该方法为private修饰的方法。
// 设置maxDelimCodePoint的值为分隔符中的最高字符,即ASCII码值最大的的字符。
private void setMaxDelimCodePoint() {
// 如果分隔符为null,
if (delimiters == null) {
maxDelimCodePoint = 0;
return;
}
int m = 0;
int c;
int count = 0;
// Character.charCount(c) 如果c大于等于 Unicode最小补充码点 返回2,否则返回1
for (int i = 0; i < delimiters.length(); i += Character.charCount(c)) {
// delimiters分隔符字符串中下标为i的字符,使用int接收,返回的是ASCII值
c = delimiters.charAt(i);
// 如果 c >= Unicode编码的最小高代理 并且 c <= Unicode编码的最大低代理。与UTF-16相关。
if (c >= Character.MIN_HIGH_SURROGATE && c <= Character.MAX_LOW_SURROGATE) {
// 获取分隔符字符串 下标为i处的字符的Unicode码点值
c = delimiters.codePointAt(i);
// 将是否使用代理设置为true
hasSurrogates = true;
}
// 比较每个分隔符的ASCII值,将最大ASCII值赋值给m
if (m < c)
m = c;
// count表示
count++;
}
// 将最大ASCII值赋值给maxDelimCodePoint
maxDelimCodePoint = m;
// 如果有代理
if (hasSurrogates) {
// 如果涉及到UTF-16和Unicode编码,将所有分隔符转换成 Unicode码点值
delimiterCodePoints = new int[count];
for (int i = 0, j = 0; i < count; i++, j += Character.charCount(c)) {
c = delimiters.codePointAt(j);
delimiterCodePoints[i] = c;
}
}
}
注意:该方法为private修饰的方法
// 如果retDelims为false(不将分隔符作为令牌返回的标志),则返回当前位置或当前位置之后第一个非分隔符的字符索引
// 如果retDelims为true(将分隔符作为令牌返回的标志),返回当前位置字符的索引
private int skipDelimiters(int startPos) {
// 分隔符字符串非空校验
if (delimiters == null)
throw new NullPointerException();
// 记录字符串下标位置,即当前判断的位置
int position = startPos;
// retDelims 一般设置为false,表示不将分隔符作为令牌返回的标志。
while (!retDelims && position < maxPosition) {
// hasSurrogates默认为false,表示没有代理,即不涉及UTF-16和Unicode编码
if (!hasSurrogates) {
// 从被分割字符串str中获取当前位置的字符
char c = str.charAt(position);
// 如果 当前位置的字符 大于 分隔符中的最大字符 或者 分隔符字符串中没有当前字符
if ((c > maxDelimCodePoint) || (delimiters.indexOf(c) < 0))
// 跳出循环
break;
// 当前位置 +1
position++;
} else {
// 存在代理(涉及UTF-16和Unicode编码),获取当前位置字符的Unicode码点值
int c = str.codePointAt(position);
// 如果当前位置的字符码点值大于分隔符中的最大字符 或者 当前码点至对应的字符不是分隔符
if ((c > maxDelimCodePoint) || !isDelimiter(c)) {
// 跳出本次循环
break;
}
// 如果c大于等于 Unicode最小补充码点 当前位置+2,否则,当前位置+1
position += Character.charCount(c);
}
}
return position;
}
注意:该方法为private修饰的方法
// 判断码点值对应的字符是不是分隔符,返回true表示当前码点值是一个分隔符,返回false表示当前的码点值不是分隔符的码点值
private boolean isDelimiter(int codePoint) {
// 涉及到代理(即涉及到UTF-16和Unicode编码),从分隔符成码点值数组中遍历
for (int delimiterCodePoint : delimiterCodePoints) {
// 如果分隔符成码点值数组中的某一个值等于当前码点值
if (delimiterCodePoint == codePoint) {
// 返回true
return true;
}
}
// 返回false
return false;
}
注意:该方法为private修饰的方法
// 从startPos跳过并返回遇到的下一个分隔符字符的索引,如果没有找到这样的分隔符,则返回maxPosition。
private int scanToken(int startPos) {
int position = startPos;
// 当要操作的字符索引小于被分割字符串的长度
while (position < maxPosition) {
// 当不包含代理,即不涉及到UTF-16和Unicode编码。
if (!hasSurrogates) {
// 获取当前索引位置的字符
char c = str.charAt(position);
// 当前字符 <= 分隔符最大字符 并且 当前字符是一个分隔符。找到分隔符时推出
if ((c <= maxDelimCodePoint) && (delimiters.indexOf(c) >= 0))
// 退出循环
break;
// 当前索引位置+1
position++;
} else {
// 获取当前索引位置对应的字符的Unicode码值
int c = str.codePointAt(position);
// 当前字符 <= 分隔符最大字符 并且 当前字符是一个分隔符。找到分隔符时推出
if ((c <= maxDelimCodePoint) && isDelimiter(c))
break;
// 涉及到Unicode编码,需要判断当前字符是占一个位置还是两个位置
position += Character.charCount(c);
}
}
// 需要将分隔符作为令牌返回的标志,并且 当前位置与起始位置一直
if (retDelims && (startPos == position)) {
// 当不包含代理,即不涉及到UTF-16和Unicode编码。
if (!hasSurrogates) {
char c = str.charAt(position);
// 当前字符 <= 分隔符最大字符 并且 当前字符是一个分隔符。
if ((c <= maxDelimCodePoint) && (delimiters.indexOf(c) >= 0))
position++;
} else {
int c = str.codePointAt(position);
// 当前字符 <= 分隔符最大字符 并且 当前字符是一个分隔符。
if ((c <= maxDelimCodePoint) && isDelimiter(c))
// // 涉及到Unicode编码,需要判断当前字符是占一个位置还是两个位置
position += Character.charCount(c);
}
}
// 返回分隔符的索引值
return position;
}
1、可以使用以下方式创建一个spring容器
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext();
// 设置配置文件路径,多个位置使用逗号、分号、空格、制表符、换行 五种方式分割。
classPathXmlApplicationContext.setConfigLocation("classpath*:/*.xml");
classPathXmlApplicationContext.refresh();
}
2、main方法中创建ClassPathXmlApplicationContext对象后会调用setConfigLocation(String location)方法。
该方法是AbstractRefreshableConfigApplicationContext类中的方法。源代码如下:
public void setConfigLocation(String location) {
// 将传入的字符串,根据逗号、分号、空格、制表符、换行 五种字符拆分成数组
setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
}
该方法中的CONFIG_LOCATION_DELIMITERS是ConfigurableApplicationContext接口中定义的常量。
String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
3、setConfigLocation(String location)方法会调用StringUtils中的方法。源代码如下:
// 根据 delimiters字符串中的字符作为分隔符,将str拆分成数组
public static String[] tokenizeToStringArray(@Nullable String str, String delimiters) {
return tokenizeToStringArray(str, delimiters, true, true);
}
4、调用StringUtils中重载的setConfigLocation方法,源代码如下:
1)该方法中调用了StringTokenizer类中的hasMoreTokens()方法作为循环条件。
2)该方法中调用StringTokenizer类中的nextToken()方法获取下一个根据分隔符拆分的字符串。
通过这两个方法,将设置配置文件的字符串根据指定的分隔符拆分。
// str 要处理的字符串
// delimiters 分隔符,每个字符都是一个分隔符
// trimTokens 拆分出来的字符串是否要去掉首尾的空格。true表示去掉
// ignoreEmptyTokens 是否忽略空字符串。true表示忽略
public static String[] tokenizeToStringArray(
@Nullable String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
if (str == null) {
return EMPTY_STRING_ARRAY;
}
// 将要操作的字符串和分隔符字符串设置到StringTokenizer对象中
StringTokenizer st = new StringTokenizer(str, delimiters);
List<String> tokens = new ArrayList<>();
while (st.hasMoreTokens()) {
String token = st.nextToken();
if (trimTokens) {
token = token.trim();
}
if (!ignoreEmptyTokens || token.length() > 0) {
tokens.add(token);
}
}
// List集合转数组
return toStringArray(tokens);
}
5、直接使用ClassPathXmlApplicationContext(String configLocation)有参构造,直接设置了配置文件路径,拆分方式与无参构造不同。