Java:StringTokenizer,根据指定分隔符拆分字符串

StringTokenizer的成员变量

// 以下七个参数是在三个参数的构造方法中设置的
// 当前位置
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;

StringTokenizer 的构造方法

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();
}

StringTokenizer public修饰的方法

hasMoreTokens() 判断是否被分割字符串中是否有更多可用的标记

注意:该方法为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);
}

nextToken()获取下一个根据分隔符拆分的字符串

注意:该方法为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);
}

nextToken(String delim)根据新的分隔符获取下一个被拆分出来的字符串

注意:该方法为public修饰的方法

// 根据新的分隔符获取下一个被拆分出来的字符串
public String nextToken(String delim) {
    // 设置分隔符,字符串中的每个字符都是一个分隔符
    delimiters = delim;

    /* delimiter string specified, so set the appropriate flag.
    * 创建对象后重新设置分隔符字符串,需要将此标志设置为true。 */
    delimsChanged = true;

    // 需改分隔符字符串后需要重新设置设置maxDelimCodePoint(分隔符中的最高字符,即ASCII码值最大的的字符)
    setMaxDelimCodePoint();
    //  根据分隔符获取下一个拆分出来的字符串
    return nextToken();
}

countTokens()获取被分割的字符串中还能拆分出几个字符串。

注意:该方法为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;
}

StringTokenizer private修饰的方法

setMaxDelimCodePoint()设置分隔符中的最高字符,即ASCII码值最大的的字符。

注意:该方法为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;
        }
    }
}

skipDelimiters()获取下一个要处理字符的索引

注意:该方法为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;
}

isDelimiter()判断码点值对应的字符是不是分隔符

注意:该方法为private修饰的方法

// 判断码点值对应的字符是不是分隔符,返回true表示当前码点值是一个分隔符,返回false表示当前的码点值不是分隔符的码点值
private boolean isDelimiter(int codePoint) {
    // 涉及到代理(即涉及到UTF-16和Unicode编码),从分隔符成码点值数组中遍历
    for (int delimiterCodePoint : delimiterCodePoints) {
        // 如果分隔符成码点值数组中的某一个值等于当前码点值
        if (delimiterCodePoint == codePoint) {
            // 返回true
            return true;
        }
    }
    // 返回false
    return false;
}

scanToken(int startPos)获取下一个分隔符的索引

注意:该方法为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;
}

StringTokenizer 类的使用

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)有参构造,直接设置了配置文件路径,拆分方式与无参构造不同。

你可能感兴趣的:(Java基础,java,开发语言,spring)