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

时间:2013-09-18 11:12:24

标签: 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]。这是不正确的。

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

现在我只是继续通过空白字符串的for循环,但这感觉就像一个创可贴解决方案。有没有人知道是什么原因造成阵列前面的空白字符串?我该如何解决?

3 个答案:

答案 0 :(得分: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

如果上面代码中的最后一个条件 - index == 0为真,那么将返回带有输入字符串的单个元素数组。

现在,考虑index可以是0的情况。

  1. 根本没有匹配。 (正如上面那条评论所述)
  2. 如果在开头找到匹配项,并且匹配字符串的长度为0,那么if块中的索引值(while循环内部) ) -

    index = m.end();
    

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

  3. 所以,考虑一下你的案例:

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

    • 对于d20+2,会有两个匹配,一个在d之前,一个在+之前。因此索引值将被更新,因此上面代码中的ArrayList将被返回,其中包含空字符串作为分隔符的分割结果,该分隔符是字符串的第一个字符,如@ Stema中已解释的那样答案。

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

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

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


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

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

    为什么呢?那是因为匹配字符串的长度为1。因此,第一次匹配后的索引值 - m.end()不会是0而是1,因此不会返回单个元素数组。

答案 1 :(得分:5)

我很惊讶,案例2和3没有发生,所以真正的问题是

  

为什么“d20”和“d%”的开头有 NO 空字符串?

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

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

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

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

(?<!^)是一个外观断言,当它不在字符串的开头时是真的。

答案 2 :(得分:0)

我建议简单匹配而不是拆分:

Matcher matcher = Pattern.compile("([1-9]*)(d[0-9%]+)([+-][0-9]+)?").matcher(string);
if(matcher.matches()) {
    String first = matcher.group(1);
    // etc
}

不保证正则表达式,但我认为它会...