【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://blog.csdn.net/m0_69908381/article/details/131293596
出自【进步*于辰的博客】
1、概述
继承关系:
- java.lang.Object
- java.util.StringTokenizer
所有已实现的接口:
Enumeration<Object>
public class StringTokenizer extends Object implements Enumeration<Object>
StringTokenizer 类允许应用程序将字符串分解为
标记
\color{green}{标记}
标记。tokenization()
比 StreamTokenizer 类所使用的方法更简单。StringTokenizer 方法不区分标识符(如标记"int"
)、数和带引号的字符串,它们也不识别并跳过注释。
可以在创建时指定,也可以根据每个标记来指定
分隔符
\color{purple}{分隔符}
分隔符(分隔标记的字符)
集合
\color{purple}{集合}
集合(即构造方法中的delim
)。
StringTokenizer 的实例有两种行为方式,这取决于它在创建时使用的 returnDelims
标志的值是 true 还是 false:
- 如果标志为 false,则分隔符字符用来分隔标记。标记是连续字符(不是分隔符)的最大序列。
- 如果标志为 true,则认为那些分隔符字符本身即为标记。因此标记要么是一个分隔符字符,要么是那些连续字符(不是分隔符)的最大序列。
StringTokenizer 对象在内部维护字符串中要被标记的当前位置(属性currentPosition
)。某些操作将此当前位置移至已处理的字符后。
通过截取字符串的一个子串来返回标记,该字符串用于创建 StringTokenizer 对象。
下面是一个使用 tokenizer
的实例。代码如下:
StringTokenizer st = new StringTokenizer("this is a test");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
输出以下字符串:
this
is
a
test
StringTokenizer 是出于
兼容性
\color{red}{兼容性}
兼容性的原因而被保留的
遗留类
\color{blue}{遗留类}
遗留类(虽然在新代码中并不鼓励使用它)。建议所有寻求此功能的人使用 String 的 split()
或 java.util.regex
包。
下面的示例阐明了如何使用 String.split()
方法将字符串分解为基本标记:
String[] result = "this is a test".split("\\s");
for (int x=0; x<result.length; x++)
System.out.println(result[x]);
输出以下字符串:
this
is
a
test
从以下版本开始:
JDK1.0
另请参见:
StreamTokenizer
2、构造方法摘要
2.1 String str
为指定字符串构造一个 stringTokenizer
。
public StringTokenizer(String str) {
this(str, " \t\n\r\f", false);
}
分隔符字符串delim
默认为\t\n\r\f
,表示制表符/换行符/回车/换页符
。
调用第3项。
2.2 String str, String delim
为指定字符串构造一个 string tokenizer。
public StringTokenizer(String str, String delim) {
this(str, delim, false);
}
returnDelims
为 false,表示分隔符字符用来分隔标记。标记是连续字符(不是分隔符)的最大序列。
调用下1项。
2.3 String str, String delim, boolean returnDelims
为指定字符串构造一个 string tokenizer。
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();
}
各属性说明见第4项;关于setMaxDelimCodePoint()
,见第5.2项。
3、方法摘要
3.1 int countTokens()
计算在生成异常之前可以调用此 tokenizer
的 nextToken()
方法的次数。
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;
}
关于skipDelimiters()
,见第5.1项;关于scanToken()
,见第5.4项。
3,2 boolean hasMoreElements()
返回与 hasMoreTokens()
方法相同的值。
仅调用下1项。
3.3 boolean hasMoreTokens()
测试此 tokenizer 的字符串中是否还有更多的可用标记。
public boolean hasMoreTokens() {
newPosition = skipDelimiters(currentPosition);// 跳过分隔符
return (newPosition < maxPosition);
}
关于skipDelimiters()
,见第5.1项。
3.4 Object nextElement()
除了其声明返回值是 Object 而不是 String 之外,它返回与 nextToken()
方法相同的值。
仅调用下1项。
3.5 String nextToken()
返回此 string tokenizer 的下一个标记。
关于skipDelimiters()
,见第5.1项;关于scanToken()
,见第5.4项;关于substring()
,见 String 类的第2.47项。
3.6 String nextToken(String delim)
根据指定分隔符修改分隔符字符串并返回此 stringtokenizer
的字符串中的下一个标记。
public String nextToken(String delim) {
delimiters = delim;
// 此属性表示“是否修改了分隔符”,作用见上1项
delimsChanged = true;
setMaxDelimCodePoint();
return nextToken();
}
关于setMaxDelimCodePoint()
,见第5.2项;关于nextToken()
,见上1项。
4、属性摘要
4.1 概况
注译:
maxDelimCodePoint
:记录具有最大值的分隔符字符的代码点。它用于优化分隔符的检测。在hasSurrogates的情况下,它不太可能提供任何优化好处,因为大多数字符串字符将小于它,但我们保留它,以便两个代码路径保持相似。hasSurrogates
:如果分隔符包含任何代理项(包括代理项对),则hasSurrogates为true,标记器使用不同的代码路径。这是因为String.indexOf(int)
不会将未配对的代理作为单个字符处理。delimiterCodePoints
:当hasSurrogates为true时,分隔符被转换为代码点记录于此数组。isDelimiter(int)
用于确定给定的代码点是否为分隔符。
4.2 属性说明
4.2.1 currentPosition、newPosition
这两个属性贯穿全局,具体不便多言,我画两张图描述它们可能的取值,方便大家理解。
示例:
new StringTokenizer("CSDN..is.platform", ".")
4.2.2 maxDelimCodePoint
- “在hasSurrogates的情况下”指的是 hasSurrogates 为 true(即分隔符字符串包含代理项)的情况,因为大多数字符串字符都达不到代理项的标准,更遑论最大值;
- “它不太可能提供任何优化好处”并不是说真的没有作用。如:
c > maxDelimCodePoint
,若为 true,表示源字符大于分隔符字符串所有字符的最大代码点,则说明源字符不可能是分隔符,则无需再在分隔符字符串中查找源字符从而判断源字符是否是分隔符(delimiters.indexOf(c)/isDelimiter(c)
)。简言之,能起到优化作用,只是由于第1点的原因使得难以奏效。 - “以便两个代码路径保持相似”的意思是:如
skipDelimiters()
,综上两点所述可知,else
中的c > maxDelimCodePoint
是不太可能提供任何优化好处,而if
中的可以。
4.2 3 hasSurrogates
“String.indexOf(int)
不会将未配对的代理作为单个字符处理”指的是此方法不具备处理增补字符的逻辑。
5、方法摘要(不开放)
5.1 private int skipDelimiters(int startPos)
返回指定位置所在连续分隔符的下一位(即普通字符)。若指定位置的字符是普通字符,则原数返回。注意:若标志是 true,直接原路返回。
private int skipDelimiters(int startPos) {
if (delimiters == null)
throw new NullPointerException();
int position = startPos;
while (!retDelims && position < maxPosition) {// 标志为 false
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);// 获取c对应的字符个数
}
}
return position;
}
关于charAt()/indexOf()/codePointAt()
,分别见 String 类的第2.1、2.23、2.2项;关于isDelimiter()
,见第3项;关于charCount()
,见字符类的第5.1项。
心路历程:
\color{purple}{心路历程:}
心路历程:
尽管我已知晓调用的各个方法的业务和其底层逻辑,可就是看不懂此方法的意图(业务)。
一开始,我猜测此方法用于跳到分隔符的前一位,即返回分隔符前一位的位置(只是猜测,无依据),一走流程就不通(delimiters.indexOf(c) < 0
)。
然后,我猜测此方法用于跳过分隔符(无论指定的位置在哪,即便隔着一段距离,都可以跳过指定位置后的分隔符,即返回指定位置后的下一个分隔符后一位的位置),依据:
countTokens()
,此方法用于计数(标记的个数),自然需要通过skipDelimiters()
跳过分隔符;delimiters.indexOf(c) < 0
,表示分隔符字符串中不包含此源字符,说明此源字符不是分隔符,即此条件是遍历分隔符字符串的终止条件。
有道理!可是又说不通。
因为此方法返回的是下一个位置(多次position++
),假设意图如上所述,则几乎所有情况下,位置根本不会移动。
真正的意图:
\color{red}{真正的意图:}
真正的意图:
此方法用于跳过分隔符,前提是指定位置的字符是分隔符。若不是,则原数返回(位置不移动,即什么也不做)。这最终的结果就是在上一个猜测上进行了完善,因为之前没有考虑到普通字符(非分隔符)走此方法流程的情况。
P S : PS: PS:已明了意图,结合调用的各个方法的业务,大家自行推演一遍,就完全理解此方法的底层逻辑了。
注意:
\color{red}{注意:}
注意:
此方法的代码路径都基于标志为 false
的条件下,即分隔符用于分隔标记,标记是连续字符(不是分隔符)的最大序列。
换言之,若标志为 false
,此方法返回位置的字符一定是普通字符,否则则可能是分隔符。
5.2 private void setMaxDelimCodePoint()
将分隔符字符串进行一些处理(如:是否包含代理项)后为此类的部分属性赋初值。
private void setMaxDelimCodePoint() {
if (delimiters == null) {
maxDelimCodePoint = 0;
return;
}
int m = 0;
int c;
int count = 0;
// 循环时若遇上增补字符,后移2位
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;
}
}
}
关于charCount()
,见字符类的第5.1项;关于charAt()/codePointAt()
,分别见 String 类的第2.1、2.2项。
先做个测试:
char c1 = Character.MAX_HIGH_SURROGATE;
char c2 = Character.MIN_LOW_SURROGATE;
c1 + 1 == c2;// true
说明只要代码点在[MIN_HIGH_SURROGATE, MAX_LOW_SURROGATE]
范围内的字符就是代理项。
c = delimiters.codePointAt(i)
与Character.charCount(c)
联合,在一定程度上判断是否是增补字符。
如果大家仍有疑惑,结合那三个方法的解析与第4项的属性说明就都明白了。
5.3 private boolean isDelimiter(int codePoint)
判断指定代码点是否是分隔符。
private boolean isDelimiter(int codePoint) {
for (int i = 0; i < delimiterCodePoints.length; i++) {
if (delimiterCodePoints[i] == codePoint) {
return true;
}
}
return false;
}
5.4 private int scanToken(int startPos)
在标志为 false 的情况下,若指定位置的字符是普通字符,则返回指定位置所在连续字符的后一位(即分隔符),否则原路返回;
在标志为 true 的情况下,若指定位置的字符是普通字符,则返回…(如前),否则返回指定位置所在连续分隔符的后一位(即普通字符)。
关于charAt()/indexOf()/codePointAt()
,分别见 String 类的第2.1、2.23、2.2项;关于isDelimiter()
,见第3项;关于charCount()
,见字符类的第5.1项。
巧妙之处,
\color{green}{巧妙之处,}
巧妙之处,
大家随意浏览一下此类的源码就会看到,在此方法的调用前必定会调用skipDelimiters()
,这说明这两个方法息息相关。如何相关?
从第1项可知,skipDelimiters()
是“不处理”普通字符的,若是分隔符则跳过,换言之,返回位置的字符一定是普通字符,这些的前提是标志为 false
。若标志为 true
,则直接return
,即此时返回位置的字符可能是普通字符,或分隔符,换言之,相当于啥也没做。
回述scanToken()
。
若标志为 false
,经skipDelimiters()
处理后,传入此方法的位置只有两种可能,(1)、首字符位置(0
);(2)、连续分隔符的下一位(即普通字符)。因此,经遍历后(第一张图),此方法返回连续字符(非分隔符)的后一位(即分隔符)。
若标志为 true
,之前的skipDelimiters()
是啥也没做。若指定位置的字符是普通字符,同上;若是分隔符,则执行第二个循环(第二张图),此方法返回连续分隔符的后一位(即普通字符)。(注:这也是跳过分隔符,同skipDelimiters()
)
这就是【概述】中所述:
连续分隔符本身即为一个标记
\color{red}{连续分隔符本身即为一个标记}
连续分隔符本身即为一个标记的底层实现逻辑。
这样就构成了一个循环,用于获取标记个数。
4、综合示例
需求:以“'.'
”和“'@'
”作为分隔符,拆分字符串"CSDN..is.platform@进步*于辰"
。
1、普通实现。
StringTokenizer tok = new StringTokenizer("CSDN..is.platform@进步*于辰", "@.");
String[] tokens= new String[tok.countTokens()];
int i = 0;
while (tok.hasMoreElements()) {
tokens[i++] = tok.nextToken();
}
Arrays.toString(tokens);// [CSDN, is, platform, 进步*于辰]
2、分析实现。
StringTokenizer tok = new StringTokenizer("CSDN..is.platform@进步*于辰", ".");
// 初始:currentPosition = 0, newPosition = -1
int count = tok.countTokens();// 3
// currentPosition和newPosition不变
boolean test = tok.hasMoreElements();// true
// currentPosition和newPosition不变
String token = tok.nextToken();// CSDN
// currentPosition = 0, newPosition = -1
// currentPosition = 4, newPosition = -1
test = tok.hasMoreElements();// true
// currentPosition = 4, newPosition = 6
token = tok.nextToken();// is
// currentPosition = 6, newPosition = 6
// currentPosition = 8, newPosition = -1
test = tok.hasMoreElements();// true
// currentPosition = 8, newPosition = 9
token = tok.nextToken();// platform@进步*于辰
// currentPosition = 9, newPosition = 9
// currentPosition = 24, newPosition = -1
Class z1 = tok.getClass();
Field f1 = z1.getDeclaredField("currentPosition");
f1.setAccessible(true);
f1.set(tok, 8);
// currentPosition = 8, newPosition = -1
test = tok.hasMoreElements();// true
// currentPosition = 8, newPosition = 9
token = tok.nextToken("@");// platform
// currentPosition = 9, newPosition = 9
// currentPosition = 18, newPosition = -1
test = tok.hasMoreElements();// true
// currentPosition = 18, newPosition = 19
token = tok.nextToken();// 进步*于辰
// currentPosition = 19, newPosition = 19
// currentPosition = 24, newPosition = -1
最后
如果大家需要Java-API文档,我上传了《Java-API文档-包含5/8/11三个版本》。
本文暂缓更新。