来自 String.split 的令人困惑的输出

Posted

技术标签:

【中文标题】来自 String.split 的令人困惑的输出【英文标题】:Confusing output from String.split 【发布时间】:2014-09-23 06:18:24 【问题描述】:

我不明白这段代码的输出:

public class StringDemo              
    public static void main(String args[]) 
        String blank = "";                    
        String comma = ",";                   
        System.out.println("Output1: "+blank.split(",").length);  
        System.out.println("Output2: "+comma.split(",").length);  
    

得到以下输出:

Output1: 1 
Output2: 0

【问题讨论】:

你有什么不明白的地方? @Raedwald 令人困惑的部分是 ",".split(",") 可以返回 ["",""] 数组,但它返回 [](空数组 - 长度 0 - 因为 split(",",0) 在末尾尾随空字符串)。那么为什么在"",split(",") 的情况下结果数组中的空字符串没有尾随? String.split 的怪异正是 Guava 库有Splitter 的原因,就像explained in the Guava documentation 一样 【参考方案1】:
String blank = "";                    
String comma = ",";                   
System.out.println("Output1: "+blank.split(",").length);  // case 1
System.out.println("Output2: "+comma.split(",").length);  // case 2

案例 1 - 这里 blank.split(",") 将返回 "" 因为在 blank 中没有 , 你得到相同的,所以长度将是 1

案例 2- 这里 comma.split(",") 将返回空数组,如果你想计算 comma 长度为 1 ,则必须转义 , 否则长度将是 0

再次 comma.split(",") split() 期望 regex 作为参数,它将返回结果数组以匹配 regex

此方法返回的数组包含此的每个子字符串 由与给定匹配的另一个子字符串终止的字符串 表达式或以字符串结尾结束。

其他

如果表达式不匹配输入的任何部分,则 结果数组只有一个元素,即这个字符串。

【讨论】:

请大致解释案例1 问题是为什么返回""【参考方案2】:

来自Java 1.7 Documentation

围绕给定正则表达式的匹配拆分字符串。

split() 方法的工作方式就像通过使用给定表达式和零限制参数调用双参数拆分方法一样。 因此,结果数组中不包含尾随的空字符串。

案例1blank.split(",") does not match any part of the input then the resulting array has just one element, namely this String.

It will return entire String. 所以,长度将是1

案例2comma.split(",") will return empty.

split() 期望正则表达式作为参数,返回结果数组以匹配该正则表达式。

所以,长度是0

例如(Documentation)

字符串 "boo:and:foo" 使用这些表达式产生以下结果:

Regex     Result
  :      "boo", "and", "foo" 
  o      "b", "", ":and:f" 

参数: regex - 分隔正则表达式

返回: 通过围绕给定正则表达式的匹配拆分此字符串计算的字符串数组

投掷: PatternSyntaxException - 如果正则表达式的语法无效

【讨论】:

docs.oracle.com/javase/7/docs/api/java/lang/… split(",") 将返回空:这需要解释! @ring0 comma.split(",") 将返回空。 split() 期望正则表达式作为参数,返回结果数组以匹配该正则表达式。所以,长度为0【参考方案3】:

文档:

发给:System.out.println("Output1: "+blank.split(",").length);

此方法返回的数组包含此字符串的每个子字符串,这些子字符串由与给定表达式匹配的另一个子字符串终止或以字符串结尾终止。数组中的子字符串按照它们在此字符串中出现的顺序排列。 如果表达式不匹配输入的任何部分,则结果数组只有一个元素,即这个字符串

它只会返回整个字符串,这就是它返回 1 的原因。


对于第二种情况,String.split 将丢弃 ,,因此结果将为空。

String.split silently discards trailing separators

也见guava StringsExplained

【讨论】:

单参数拆分方法的 Javadoc 说:“此方法的工作原理就像通过使用给定表达式和零限制参数调用双参数拆分方法一样。尾随空字符串因此不包含在结果数组中。”这是对第二个结果的正确解释。两个尾随的空字符串被排除在外。 是的,理论上一切都在文档中。但是我总是想知道他们从哪里得到那些你可以阅读他们所写内容的 10 倍的人,但你仍然必须编写一个测试程序来了解该方法实际上在做什么......【参考方案4】:

我们可以查看the source code of java.util.regex.Pattern,它位于String.split 后面。打兔子洞的方法

public String[] split(CharSequence input, int limit)

被调用。

输入""

对于输入"",这个方法被称为

String[] parts = split("", 0);

The intersting part of this method is:

  int index = 0;
  boolean matchLimited = limit > 0;
  ArrayList<String> matchList = new ArrayList<>();
  Matcher m = matcher(input);

  while(m.find()) 
    // Tichodroma: this will not happen for our input
  

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

这就是发生的事情:new String[] input.toString() is returned。

输入","

对于输入","the intersting part is

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);

这里是resultSize == 0limit == 0 所以new String[0] is returned。

【讨论】:

我相信你的最后一句话过于简单化了,以至于它削弱了你答案的价值。 interesting(即,relevant)部分是第 1223-1225 行。进入第 1223 行,resultSize2,因为 matchList"",""。但是,因为 limit0(仅使用一个参数调用 split 时的默认值),第 1224-1225 行的循环被调用,它迭代两次,丢弃这两个空字符串并将resultSize 递减为0【参考方案5】:

来自public String[] <b>split</b>(String regex) 方法的字符串类javadoc:

围绕给定正则表达式的匹配拆分此字符串。

此方法的工作方式就像通过使用给定表达式和零限制参数调用双参数拆分方法一样。因此,尾随的空字符串不包含在结果数组中。

在第一种情况下,表达式不匹配输入的任何部分,所以我们得到了一个只有一个元素的数组 - 输入。

在第二种情况下,表达式匹配输入,拆分应该返回两个空字符串;但是,根据 javadoc,它们被丢弃了(因为它们是尾随和空的)。

【讨论】:

+1 这是(嗯;在这里指望我的手指)第六 答案,它说 what 结果被返回 - 和 第一个解释为什么【参考方案6】:

split 方法的 API 声明“如果表达式与输入的任何部分都不匹配,则结果数组只有一个元素,即这个字符串。”

因此,由于字符串空白不包含“,”,因此返回带有一个元素(即空白本身)的 String[]。

对于字符串逗号,“nothing”在原始字符串的左边,因此返回一个空数组。

如果您想处理返回的结果,这似乎是最好的解决方案,例如。 g.

String[] splits = aString.split(",");
for(String split: splits) 
   // do something

【讨论】:

【参考方案7】:

从 JDK 1.7

 public String[] split(String regex, int limit) 
        /* fastpath if the regex is a
           (1)one-char String and this character is not one of the
              RegEx's meta characters ".$|()[^?*+\\", or
           (2)two-char String and the first char is the backslash and
              the second is not the ascii digit or ascii letter.
        */
        char ch = 0;
        if (((regex.count == 1 &&
             ".$|()[^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
             (regex.length() == 2 &&
              regex.charAt(0) == '\\' &&
              (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
              ((ch-'a')|('z'-ch)) < 0 &&
              ((ch-'A')|('Z'-ch)) < 0)) &&
            (ch < Character.MIN_HIGH_SURROGATE ||
             ch > Character.MAX_LOW_SURROGATE))
        
            int off = 0;
            int next = 0;
            boolean limited = limit > 0;
            ArrayList<String> list = new ArrayList<>();
            while ((next = indexOf(ch, off)) != -1) 
                if (!limited || list.size() < limit - 1) 
                    list.add(substring(off, next));
                    off = next + 1;
                 else     // last one
                    //assert (list.size() == limit - 1);
                    list.add(substring(off, count));
                    off = count;
                    break;
                
            
            // If no match was found, return this
            if (off == 0)
                return new String[]  this ;

            // Add remaining segment
            if (!limited || list.size() < limit)
                list.add(substring(off, count));

            // Construct result
            int resultSize = list.size();
            if (limit == 0)
                while (resultSize > 0 && list.get(resultSize-1).length() == 0)
                    resultSize--;
            String[] result = new String[resultSize];
            return list.subList(0, resultSize).toArray(result);
        
        return Pattern.compile(regex).split(this, limit);
    

所以对于这种情况,正则表达式将由第一个 if 处理。

对于第一种情况blank.split(",")

// If no match was found, return this
if (off == 0)
   return new String[]  this ;

所以,如果没有匹配,这个函数将返回一个包含一个元素的数组。

对于第二种情况comma.split(",")

List<String> list = new ArrayList<>();
//...
int resultSize = list.size();
if (limit == 0)
    while (resultSize > 0 && list.get(resultSize-1).length() == 0)
           resultSize--;
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);

如您所见,最后一个 while 循环已删除列表末尾的所有空元素,因此 resultSize 为 0

【讨论】:

【参考方案8】:

一切都按计划进行,但让我们一步一步来(希望你有时间)。

根据documentation(和source code)的split(String regex)方法:

此方法的工作原理就像通过使用给定表达式和零限制参数调用双参数拆分方法一样。

所以当你调用

split(String regex)

您实际上是从split(String regex, int limit) 方法获得结果,该方法以某种方式调用:

split(regex, 0)

所以这里limit 设置为0

关于这个参数你需要知道几点:

如果limit 为正数,则您将结果数组的长度限制为您指定的正数,因此"axaxaxaxa".split("x",2) 将返回一个数组["a", "axaxaxa"],而不是["a","a","a","a","a"]

如果limit0,那么您没有限制结果数组的长度。但这也意味着任何尾随的空字符串都将被删除。例如:

"fooXbarX".split("X")

将在开始时生成一个如下所示的数组:

["foo", "bar", ""]

"barX""X" 上拆分生成"bar"""),但由于split 删除了所有尾随空字符串,它会返回

["foo", "bar"]

limit 的负值行为类似于将limit 设置为0 的行为(它不会限制结果数组的长度)。唯一的区别是它不会从结果数组的末尾删除空字符串。换句话说

"fooXbarX".split("X",-1)

将返回["foo", "bar", ""]


让我们来看看案例,

",".split(",").length

which(如前所述)与

相同
",".split(",", 0).length

这意味着我们使用的 split 版本不会限制结果数组的长度,但会删除所有尾随的空字符串""。您需要了解,当我们拆分 一个 东西时,我们总是得到 两个 东西。

换句话说,如果我们拆分"abc" 代替b,我们将得到"a""c"。 棘手的部分是要理解,如果我们将"abc" 拆分为c,我们将得到"ab"""(空字符串)。

使用这个逻辑,如果我们将"," 拆分为,,我们将得到""""(两个空字符串)。

您可以使用带有负数限制的split 进行检查:

for (String s: ",".split(",", -1))
    System.out.println("\""+s+"\"");

将打印出来

""
""

所以我们在这里看到的结果数组最初是["", ""]

但由于默认情况下我们使用 limit 设置为 0,所有尾随的空字符串都将被删除。在这种情况下,结果数组只包含尾随的空字符串,因此所有这些都将被删除,留下空数组[],其长度为0


回答这个问题

"".split(",").length

您需要了解仅当此类尾随空字符串是拆分结果(并且很可能不需要)时,删除尾随空字符串才有意义。 因此,如果没有我们可以拆分的任何地方,就不可能创建空字符串,因此运行这个“清理”过程是没有意义的。

此信息在documentation of split(String regex, int limit)方法中提及,您可以阅读:

如果表达式不匹配输入的任何部分,则结果数组只有一个元素,即这个字符串

您还可以在 source code of this method(来自 Java 8)中看到这种行为:

2316 public String[] split(String regex, int limit) 2317 /* 快速路径,如果 regex 是a2318 (1)one-char 字符串,并且此字符不是2319 RegEx 的元字符“.$|()[^?*+\\”之一,或2320 (2)双字符字符串,第一个字符是反斜杠 2321 第二个不是 ascii 数字或 ascii 字母。2322 */2323 char ch = 0;2324 if (((regex.value.length == 1 &&2325 ".$|() [^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||2326 (regex.length() == 2 && 2327 regex.charAt(0) == '\\' &&2328 (((ch = regex.charAt(1))-'0')|('9 '-ch)) 2329 ((ch-'a')|('z'-ch)) 2330 ((ch-'A')|( 'Z'-c h)) 2331 (ch 2332 ch > Character.MAX_LOW_SURROGATE))2333 2334 int off = 0;2335 int next = 0;2336 boolean limited = limit > 0;2337ArrayListString> list = new ArrayList();2338 while ((next = @987654358 @(ch, off)) != -1) 2339 if (!limited || list.size() 2340 list.add(substring(off, next));2341 off = next + 1;2342  else  // 最后一个2343 //assert (list.size() == limit - 1);2344 list.add (substring(off, value.length));2345 off = value.length;2346 break;2347 2348 2349 // 如果没有找到匹配项,返回这个2350 if (off == 0)2351 return  new String[]this;2353 // 添加剩余段2354 if (!limited || list.size() 2355 list.add(substring(off, value.length));2357 //构造结果2358 int resultSize = list.size();2359 if (limit == 0) 2360 while (resultSize > 0 && list.get(resultSize - 1).length() == 0) 2361resultSize--;2362 2363 2364 String[] 结果 =  String[resultSize];2365 返回 list.subList(0, resultSize).toArray(result);2366 2367 return Pattern.compile(regex) .split(this, limit);2368 

在哪里可以找到

if (off == 0)
    return new String[]this;

片段表示

if (off == 0) - 如果off(从哪个方法开始搜索作为split 参数传递的正则表达式的下一个可能匹配项的位置)在遍历整个字符串后仍然是0,我们没有'找不到任何匹配项,所以字符串没有被拆分 return new String[]this; - 在这种情况下,我们只返回一个带有原始字符串的数组(由 this 表示)。

由于在"" 中甚至一次都找不到",",因此"".split(",") 必须返回一个包含一个元素的数组(您调用split 的空字符串)。这意味着这个数组的长度是1

顺便说一句。 Java 8 引入了另一种机制。如果我们使用zero-length regex(如"" 或环视(?&lt;!x))进行拆分,它会删除前导空字符串(如果它们是在拆分过程中创建的)。更多信息请访问:Why in Java 8 split sometimes removes empty strings at start of result array?

【讨论】:

对不起,题外话了,请问您是如何生成带有行号和格式的代码块的? @Bob 当您将鼠标悬停在 grepcode 的行号上时,您会看到 &lt;&gt;。当您单击它时,您将打开一个框,您可以在其中指定要作为 html 代码获取的行范围。 啊,有点遗憾,它是特定于 grepcode 的,但仍然相当不错。谢谢。 @AbhinavVutukuri 要回答您的问题,我需要不止一条评论。您能否将这些示例作为单独的问题(或多个问题)发布?指出您使用的 Java 版本可能也很重要。从您的个人资料图片来看,它可能是使用 Java 7 而不是 Java 8 的 android,您可以得到不同的结果。 @AbhinavVutukuri 总而言之,您可以认为 Java 假定您不能将"" 拆分得更远,因此对于每个"".split(whatever),您将始终得到[""] 数组。如果",".split(",") 正则表达式匹配整个字符串,所以首先你得到["", ""] 数组,然后删除尾随的空字符串,留下空数组,所以它的长度是0 而不是2(我不知道你在哪里从中获取该值)。 Java 8 中的" ".split("") 给了我[" "]。最初是["", " ", ""] - 空字符串存在于字符串的开头和结尾。删除了尾随的空字符串,在 Java 8 中处于领先地位。

以上是关于来自 String.split 的令人困惑的输出的主要内容,如果未能解决你的问题,请参考以下文章

来自操纵杆 Unity 的令人困惑的输入

C# String.split()用法小结。String.Split 方法 (String[],?StringSplitOptions)

EM Clustering with weka 对于某些集群的对数可能性为 0?令人困惑的输出

delphi string.split 按照任意字符串分割语句

docker运行“--cpus”标志令人困惑

令人困惑的TensorFlow!