Java中的StringTokenizer实现字符串切割详解

 更新时间:2024年01月31日 10:50:42   作者:小徐也要努力鸭  
这篇文章主要介绍了Java中的StringTokenizer实现字符串切割详解,java.util工具包提供了字符串切割的工具类StringTokenizer,Spring等常见框架的字符串工具类(如Spring的StringUtils),需要的朋友可以参考下

前言

java.util工具包提供了字符串切割的工具类StringTokenizer,Spring等常见框架的字符串工具类(如Spring的StringUtils),常见此类使用。

例如Spring的StringUtils下的方法:

public static String[] tokenizeToStringArray(
		@Nullable String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {

	if (str == null) {
		return EMPTY_STRING_ARRAY;
	}

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

又如定时任务框架Quartz中,cron表达式类CronExpression,其中的buildExpression方法是为了处理cron表达式的,cron表达式有7个子表达式,空格隔开,cron表达式字符串的切割也使用到了StringTokenizer类,方法如下:

protected void buildExpression(String expression) throws ParseException {
    this.expressionParsed = true;
    try {
        if (this.seconds == null) {
            this.seconds = new TreeSet();
        }
        if (this.minutes == null) {
            this.minutes = new TreeSet();
        }
        if (this.hours == null) {
            this.hours = new TreeSet();
        }
        if (this.daysOfMonth == null) {
            this.daysOfMonth = new TreeSet();
        }
        if (this.months == null) {
            this.months = new TreeSet();
        }
        if (this.daysOfWeek == null) {
            this.daysOfWeek = new TreeSet();
        }
        if (this.years == null) {
            this.years = new TreeSet();
        }
        int exprOn = 0;
        for(StringTokenizer exprsTok = new StringTokenizer(expression, " \t", false); exprsTok.hasMoreTokens() && exprOn <= 6; ++exprOn) {
            String expr = exprsTok.nextToken().trim();
            if (exprOn == 3 && expr.indexOf(76) != -1 && expr.length() > 1 && expr.contains(",")) {
                throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
            }
            if (exprOn == 5 && expr.indexOf(76) != -1 && expr.length() > 1 && expr.contains(",")) {
                throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
            }
            if (exprOn == 5 && expr.indexOf(35) != -1 && expr.indexOf(35, expr.indexOf(35) + 1) != -1) {
                throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
            }
            StringTokenizer vTok = new StringTokenizer(expr, ",");
            while(vTok.hasMoreTokens()) {
                String v = vTok.nextToken();
                this.storeExpressionVals(0, v, exprOn);
            }
        }
        if (exprOn <= 5) {
            throw new ParseException("Unexpected end of expression.", expression.length());
        } else {
            if (exprOn <= 6) {
                this.storeExpressionVals(0, "*", 6);
            }
            TreeSet<Integer> dow = this.getSet(5);
            TreeSet<Integer> dom = this.getSet(3);
            boolean dayOfMSpec = !dom.contains(NO_SPEC);
            boolean dayOfWSpec = !dow.contains(NO_SPEC);
            if ((!dayOfMSpec || dayOfWSpec) && (!dayOfWSpec || dayOfMSpec)) {
                throw new ParseException("Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
            }
        }
    } catch (ParseException var8) {
        throw var8;
    } catch (Exception var9) {
        throw new ParseException("Illegal cron expression format (" + var9.toString() + ")", 0);
    }
}

使用方法

import com.google.common.collect.Lists;
import java.util.List;
import java.util.StringTokenizer;
/**
 * @author xiaoxu
 * @date 2023-10-18
 * spring_boot:com.xiaoxu.boot.tokenizer.TestStringTokenizer
 */
public class TestStringTokenizer {
    public static void main(String[] args) {
        print("你 好 吗\t我是 \t你的\t 朋友 \t", " \t", false);
    }
    public static void print(String str, String delimiter, boolean isReturnDelims) {
        System.out.println("切割字符串:【" + str + "】;" + "分隔符:【" + delimiter + "】。");
        List<String> strs = Lists.newArrayList();
        String s;
        boolean x;
        for (StringTokenizer strToken = new StringTokenizer(str, delimiter, false); strToken.hasMoreTokens(); x = (s != null && strs.add(s))) {
            s = strToken.nextToken();
            System.out.println("切割:【" + s + "】");
            if(s.equals("吗"))
                s = null;
        }
        System.out.println("字符串数组:" + strs);
    }
}

执行结果:

切割字符串:【你 好 吗    我是     你的     朋友     】;分隔符:【     】。
切割:【你】
切割:【好】
切割:【吗】
切割:【我是】
切割:【你的】
切割:【朋友】
字符串数组:[你, 好, 我是, 你的, 朋友]

源码片段分析

public StringTokenizer(String str, String delim, boolean returnDelims) {
    currentPosition = 0;
    newPosition = -1;
    delimsChanged = false;
    this.str = str;
    maxPosition = str.length();
    delimiters = delim;
    retDelims = returnDelims;
    setMaxDelimCodePoint();
}
private void setMaxDelimCodePoint() {
    if (delimiters == null) {
        maxDelimCodePoint = 0;
        return;
    }
    int m = 0;
    int c;
    int count = 0;
    for (int i = 0; i < delimiters.length(); i += Character.charCount(c)) {
        c = delimiters.charAt(i);
        if (c >= Character.MIN_HIGH_SURROGATE && c <= Character.MAX_LOW_SURROGATE) {
            c = delimiters.codePointAt(i);
            hasSurrogates = true;
        }
        if (m < c)
            m = c;
        count++;
    }
    maxDelimCodePoint = m;
    if (hasSurrogates) {
        delimiterCodePoints = new int[count];
        for (int i = 0, j = 0; i < count; i++, j += Character.charCount(c)) {
            c = delimiters.codePointAt(j);
            delimiterCodePoints[i] = c;
        }
    }
}

调用setMaxDelimCodePoint()方法,源码可知,切割时设置int maxDelimCodePoint,是为了优化分隔符的检测(取的是分隔字符串中char的ASCII码值最大的字符的ASCII值,存入maxDelimCodePoint中。在方法int scanToken(int startPos)中,若满足条件(c <= maxDelimCodePoint) && (delimiters.indexOf© >= 0),意即该字符的ASCII码值小于等于最大的maxDelimCodePoint,那么这个字符可能存在于分隔字符串中,再检测delimiters分隔字符串中是否包含该字符,反之,若ASCII码值大于分隔字符串中最大的maxDelimCodePoint,也就是说该字符一定不存在于分隔字符串里,&&直接跳过delimiters.indexOf的检测,也就达到了优化分隔符检测的效果了)。

private int scanToken(int startPos) {
    int position = startPos;
    while (position < maxPosition) {
        if (!hasSurrogates) {
            char c = str.charAt(position);
            if ((c <= maxDelimCodePoint) && (delimiters.indexOf(c) >= 0))
                break;
            position++;
        } else {
            int c = str.codePointAt(position);
            if ((c <= maxDelimCodePoint) && isDelimiter(c))
                break;
            position += Character.charCount(c);
        }
    }
    if (retDelims && (startPos == position)) {
        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))
                position += Character.charCount(c);
        }
    }
    return position;
}

scanToken方法即跳过分隔字符串,只要某此循环时,该字符包含在分隔字符串里,那么position不再自增,以此时的position值作为实际切割获取字符串的末索引, 因为subString方法是左闭右开的,该值是实际获取字符串的末索引值+1,所以可以截取到完整的不包含分隔符的字符串片段。

skipDelimiters方法类似,即过滤连续包含于分隔字符串中的字符,获取实际需要切割获取的字符串的开始索引值。

private int skipDelimiters(int startPos) {
    if (delimiters == null)
        throw new NullPointerException();
    int position = startPos;
    while (!retDelims && position < maxPosition) {
        if (!hasSurrogates) {
            char c = str.charAt(position);
            if ((c > maxDelimCodePoint) || (delimiters.indexOf(c) < 0))
                break;
            position++;
        } else {
            int c = str.codePointAt(position);
            if ((c > maxDelimCodePoint) || !isDelimiter(c)) {
                break;
            }
            position += Character.charCount(c);
        }
    }
    return position;
}

上述分析可知,只要待切割字符串中的字符,在分隔字符串中出现,那么就会做一次切割(也就是不论分隔字符串中的每个char或字符串片段的顺序,只要连续包含在分隔字符串里,就切割)。

演示如下(注意countTokens()方法不要在循环中和nextToken()一同使用):

public static void print2(String str, String delimiter, boolean isReturnDelims) {
    StringTokenizer strTokenizer = new StringTokenizer(str, delimiter);
    System.out.println("总数目:" + strTokenizer.countTokens());
    int count;
    String[] strs = new String[count = strTokenizer.countTokens()];
    // 注意:不要在循环里写 int i = 0; i < strTokenizer.countTokens();
    // 因为  countTokens方法需要使用currentPosition,而每次执行nextToken方法时,currentPosition会一直往下偏移计算,
    // 会导致循环中, i < strTokenizer.countTokens();发生改变,这里应该是常量总数目
    for (int i = 0; i < count; i++) {
        String s = strTokenizer.nextToken();
        strs[i] = s;
    }
    System.out.println(Arrays.toString(strs));
}

countTokens源码如下:

public int countTokens() {
    int count = 0;
    int currpos = currentPosition;
    while (currpos < maxPosition) {
        currpos = skipDelimiters(currpos);
        if (currpos >= maxPosition)
            break;
        currpos = scanToken(currpos);
        count++;
    }
    return count;
}

执行:

print2("1a2b3c4ca5bc6ba7abc8acbbaba9", "abc", false);

结果如下所示:

总数目:9
[1, 2, 3, 4, 5, 6, 7, 8, 9]

到此这篇关于Java中的StringTokenizer实现字符串切割详解的文章就介绍到这了,更多相关StringTokenizer字符串切割内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java 实战项目之教材管理系统的实现流程

    Java 实战项目之教材管理系统的实现流程

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用java+SSM+jsp+mysql+maven实现教材管理系统,大家可以在过程中查缺补漏,提升水平
    2021-11-11
  • Java Annotation详解及实例代码

    Java Annotation详解及实例代码

    这篇文章主要介绍了Java Annotation详解及实例代码的相关资料,需要的朋友可以参考下
    2017-03-03
  • C++ 程序流程结构详解

    C++ 程序流程结构详解

    这篇文章主要介绍了C++程序的程序流程结构以及关系和逻辑运算符讲解,是C++入门学习中的基础知识,需要的朋友可以参考下
    2021-09-09
  • Java Kafka 消费积压监控的示例代码

    Java Kafka 消费积压监控的示例代码

    这篇文章主要介绍了Java Kafka 消费积压监控,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-07-07
  • @Scheduled定时器使用注意事项及说明

    @Scheduled定时器使用注意事项及说明

    这篇文章主要介绍了@Scheduled定时器使用注意事项及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • 通过Java组合问题看透回溯法

    通过Java组合问题看透回溯法

    今天给大家分享一道LeetCode算法题,题目不是很困难,但是从这到简单的题目我们可以分析出回溯算法的几个核心要点,感兴趣的可以了解一下
    2022-09-09
  • 通过简单步骤实现SpringMVC文件上传

    通过简单步骤实现SpringMVC文件上传

    这篇文章主要介绍了通过简单步骤实现SpringMVC文件上传,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • 解析Java异步之call future

    解析Java异步之call future

    当调用一个函数的时候,如果这个函数的执行过程是很耗时的,就必须要等待,但是有时候并不急着要这个函数返回的结果。因此,可以让被调者立即返回,让他在后台慢慢处理这个请求。对于调用者来说,可以先处理一些其他事情,在真正需要数据的时候再去尝试获得需要的数据
    2021-06-06
  • Spring中的父子容器原理解析

    Spring中的父子容器原理解析

    这篇文章主要为大家介绍了Spring中的父子容器原理解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • SpringBoot测试配置属性与web启动环境超详细图解

    SpringBoot测试配置属性与web启动环境超详细图解

    Web开发的核心内容主要包括内嵌的Servlet容器和SpringMVCSpringBoot使用起来非常简洁,大部分配置都有SpringBoot自动装配,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-10-10

最新评论