Java String.split()有时会给出空字符串

Cor*_*oel 13 java regex string split

我正在制作一个基于文本的骰子滚筒.它接受像"2d10 + 5"这样的字符串,并且作为滚动的结果返回一个字符串.我的问题出现在tokenizer中,它将字符串拆分为有用的部分,以便我解析信息.

String[] tokens = message.split("(?=[dk\\+\\-])");

这会产生奇怪的,意想不到的结果.我不知道究竟是什么导致了他们.它可能是正则表达式,我的误解,或Java只是Java.这是发生了什么:

  • 3d6+4产生字符串数组[3, d6, +4].这是对的.
  • d%产生字符串数组[d%].这是对的.
  • d20产生字符串数组[d20].这是对的.
  • d%+3产生字符串数组[, d%, +3].这是不正确的.
  • d20+2产生字符串数组[, d20, +2].这是不正确的.

在第四和第五个例子,一些奇怪的从而导致额外的空字符串出现在阵列的前部.这并不是字符串前面缺少数字,因为其他例子反驳了这一点.这不是百分号的存在,也不是加号.

现在我只是通过对空字符串循环继续,但那种感觉八九不离十像创可贴的解决方案.有没有人知道是什么原因造成阵列前面的空白字符串?我该如何解决?

Roh*_*ain 13

通过源代码挖掘,我得到了这种行为背后的确切问题.

String.split()方法内部使用Pattern.split().返回结果数组之前的split方法检查最后匹配的索引或实际是否匹配.如果最后匹配的索引是0,这意味着您的模式在字符串的开头只匹配一个空字符串或根本不匹配,在这种情况下,返回的数组是包含相同元素的单个元素数组.

这是源代码:

public String[] split(CharSequence input, int limit) {
        int index = 0;
        boolean matchLimited = limit > 0;
        ArrayList<String> matchList = new ArrayList<String>();
        Matcher m = matcher(input);

        // Add segments before each match found
        while(m.find()) {
            if (!matchLimited || matchList.size() < limit - 1) {
                String match = input.subSequence(index, m.start()).toString();
                matchList.add(match);

                // Consider this assignment. For a single empty string match
                // m.end() will be 0, and hence index will also be 0
                index = m.end();
            } else if (matchList.size() == limit - 1) { // last one
                String match = input.subSequence(index,
                                                 input.length()).toString();
                matchList.add(match);
                index = m.end();
            }
        }

        // If no match was found, return this
        if (index == 0)
            return new String[] {input.toString()};

        // Rest of them is not required
Run Code Online (Sandbox Code Playgroud)

如果上面代码中的最后一个条件 - index == 0,为真,则返回单个元素数组和输入字符串.

现在,考虑的情况下,当index0.

  1. 什么时候根本没有比赛.(正如上面那条评论那样)
  2. 如果在开头找到匹配,并且匹配字符串的长度是0,那么if块中的索引值(在while循环内) -

    index = m.end();
    
    Run Code Online (Sandbox Code Playgroud)

    将为0.唯一可能的匹配字符串是空字符串(长度= 0).这就是这种情况.而且不应该有任何进一步的匹配,否则index将更新为不同的索引.

所以,考虑你的情况:

  • 因为d%,在第一个模式之前,模式只有一个匹配d.因此指数值将是0.但由于没有任何进一步的匹配,索引值不会更新,if条件变为true,并返回带有原始字符串的单个元素数组.

  • 对于d20+2将有两场比赛,一个前d前,和一个+.因此索引值将被更新,因此ArrayList将返回上面的代码,其中包含空字符串作为分隔符分割的结果,分隔符是字符串的第一个字符,如@Stema的答案中所述.

因此,要获得所需的行为(仅在不在开头时分割为分隔符,您可以在正则表达式模式中添加负面的后视):

"(?<!^)(?=[dk+-])"  // You don't need to escape + and hyphen(when at the end)
Run Code Online (Sandbox Code Playgroud)

这将拆分为空字符串,后跟您的字符类,但不会在字符串的开头之前.


考虑"ad%"在正则表达式模式上拆分字符串的情况- "a(?=[dk+-])".这将为您提供一个数组,其中第一个元素为空字符串.这里唯一的变化是,空字符串被替换为a:

"ad%".split("a(?=[dk+-])");  // Prints - `[, d%]`
Run Code Online (Sandbox Code Playgroud)

为什么?那是因为匹配字符串的长度是1.因此,第一个匹配后的指标值- m.end()不会0但是1,因此,单个元件阵列将不会被返回.


ste*_*ema 5

我很惊讶它不会发生在案例2和3中,所以真正的问题是

为什么"d20"和"d%"的开头没有空字符串?

正如Rohit Jain在他的详细分析中解释的那样,当在字符串的开头只找到一个匹配并且match.end索引为0时,会发生这种情况.(这只有在使用环绕声断言才能找到时才会发生.比赛).

问题是,d%+3从你正在分裂的char开始.所以你的正则表达式在第一个字符之前匹配,你在开始时得到一个空字符串.

您可以添加一个lookbehind,以确保您的表达式在字符串的开头不匹配,以便它不会在那里拆分:

String[] tokens = message.split("(?<!^)(?=[dk\\+\\-])");
Run Code Online (Sandbox Code Playgroud)

(?<!^) 当它不在字符串的开头时,是一个看起来很好的断言.

  • 你的解决方案做到了.坚持前面的(?<!^)解决了这个问题,虽然我仍然不能完全确定为什么它出现了一些次而不是其他次. (2认同)