字符串分词器的奇怪行为

Strange Behaviour of String Tokenizer

我有一个带分隔符的字符串 (~)

    String str="ABC~DEF~GHI~JKL~~MNO";// Input String
     while(stk.hasMoreTokens()){
            obj[i]=stk.nextToken();
            i++;
        }
        for(Object ob:obj){
            System.out.print(ob+"~>");
        }

我正在使用 StringTokenizer 将字符串分解为标记,但是每当 consecutive delimeter 出现在中间而没有任何 Space 时,StringTokenizer 会跳过它并获取下一个标记

实际输出

ABC~>DEF~>GHI~>JKL~>MNO~>null~>

期望输出

ABC~>DEF~>GHI~>JKL~>null~>MNO~> // Don't want to skip consecutive tokens

为什么会这样?

注:

我知道我可以使用 String#split(String delimeter) 方法获得所需的输出,但是,我想知道出现奇怪行为的根本原因。

这里有人问过同样的问题 (String Tokenizer issue) 但没有提供原因,只有替代解决方案

我假设你用过 new StringTokenizer(str,"~")

StringTokenizer使用token的定义:一个token是一个maximum non empty char sequence sequence between delimiters.

由于 ~~ 之间的字符串为空,因此它不能是标记(根据此定义)。

我用下面的代码来验证:

public static void main(String[] args) {
    List<Object> obj = new ArrayList<>();
    String str = "ABC~DEF~GHI~JKL~~MNO";// Input String
    StringTokenizer stk = new StringTokenizer(str,"~");
    while (stk.hasMoreTokens()) {
        obj.add(stk.nextToken());
    }
    for (Object ob : obj) {
        System.out.print(ob + "~>");
    }
}

实际产出(与token定义一致)

ABC~>DEF~>GHI~>JKL~>MNO~>

如果问题是:为什么要这样定义令牌?看这个例子:

String str = "ABC DEF GHI"; // two spaces between

Stringtokenizer 找到 3 个标记。如果你不强制一个标记为非空,这将是 return 5 个标记(2 个是“”)。如果您编写一个简单的解析器,则当前行为更可取。

您无法让 StringTokenizer 按您希望的方式工作(它永远不会 returns 空白),但您可以改用 String#split()

for (String token : str.split("~")) {
    // there will be a blank token where you expect it
}

此外,这段代码也简单多了。

nextToken()方法调用skipDelimiter(int startPos)方法寻找下一个token的索引。

/**
 * Skips delimiters starting from the specified position. If retDelims
 * is false, returns the index of the first non-delimiter character at or
 * after startPos. If retDelims is true, startPos is returned.
 */
private int skipDelimiters(int startPos)

因为 ~~ 之间没有字符串,所以它的行为是正确的。

文档中也清楚的写着:

StringTokenizer is a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the split method of String or the java.util.regex package instead. 

StringTokenizer 有一个私有标志 (returnDelims),默认情况下为 false。写成

如果 returnDelims 标志为真,则分隔符也作为标记返回。每个定界符都作为长度为 1 的字符串返回。如果标志为 false,分隔符将被跳过,仅用作标记之间的分隔符。

StringTokenizer 有另一个用于设置值的构造函数。为了您的目的,您应该将 true 传递给 returnDelims 标志,就像这样

    String str = "ABC~DEF~GHI~JKL~~MNO";// Input String
    final String token = "~";
    StringTokenizer stk = new StringTokenizer(str, token, true);
    Object[] obj = new Object[10];
    int i = 0;
    String lasToken = "";
    while (stk.hasMoreTokens()) {
        String nexToken = stk.nextToken();
        if (!token.equals(nexToken)) {
            obj[i] = nexToken;
            i++;
        } else if (token.equals(lasToken)) {
            i++;
        }
        lasToken = nexToken;
    }
    for (i = 0; i < obj.length; i++) {
        System.out.print(obj[i] + "~>");
    }