当前位置: 首页 > 后端技术 > Java

Java字符串拆分的一个反直觉的陷阱

时间:2023-04-01 15:08:43 Java

最近生产环境遇到了一个奇怪的数组下标越界错误,如下代码所示,我们可以确定fieldName变量不为空(不是一个空字符串,也不是null),但是当代码执行读取names[0]变量时,抛出数组下标越界异常(java.lang.ArrayIndexOutOfBoundsException)。异常信息如下图所示。问题很简单。我们对一个字符串执行split方法后,根据以往其他编程语言(Go、PHP、Javascript、Dart等)的经验,即使字符串为空,即使没有匹配到分隔符,当前字符串的值也将包含在返回值数组中。但是这里会抛出ArrayIndexOutOfBoundsException。有没有可能split方法的返回值是一个空数组?最后,经过排查,发现在上述代码段中,当fieldName的值为“~”时,我们访问names[0]时,会抛出ArrayIndexOutOfBoundsException。为什么会这样?本文将不断修订更新。最新内容可以参考我的GITHUB上的程序员成长计划项目。欢迎来到星空。更多精彩内容,请关注我。问题出在Java中,如果你执行下面的代码,你直观的认为会输出什么?Stringstr="~";String[]arr=str.split("~");System.out.println(arr.length);如果你有其他编程语言的经验,你可能会直观地觉得这里的输出应该是2,但不幸的是,这里的输出是0,变量arr是一个空数组。这里不禁怀疑是不是自己之前的记忆有偏差,所以使用其他语言尝试重现这个问题。split在不同语言下的行为我总结了一个表格来说明不同语言下的不同行为。这里比较的是执行split函数/方法后返回数组的长度:language\function"".split("")"~"。split("~")"~~".split("~")"".split("~")"~123".split("~")Javascript02312PHP02312Dart02312Golang02312Scala10012Java10012Javascript首先是Javascript,直接在浏览器的控制台执行,得到如下结果"".split("")"~".split("~")"~~".split("~")"".split("~")"~123"。split("~")的执行结果和我的直觉是一致的。同样的情况,这里返回2。PHP在PHP中,我使用mb_split函数,它用于拆分多字节字符串。执行结果如下。执行结果也符合我的直觉。同样的情况,这里返回的值为2。Dart,然后是Google的Dart,这是一门主要用来开发Flutter跨平台应用程序的编程语言,代码如下voidmain(){print("".split('').length;//0print("~".split('~').length);//2print("~~".split('~').length);//3print("".split('~').length);//1print("~123".split('~').length);//2}执行结果同样,"~".split("~")也返回两个值。Golang在Golang中,执行结果还是比较直观的,返回2。packagemainimport("strings""fmt")funcmain(){printStrs(strings.Split("",""))//0[]printStrs(strings.Split("~","~"))//2["","",]printStrs(strings.Split("~~","~"))//3["","","",]printStrs(strings.Split("","~)"))//1["",]printStrs(strings.Split("~123","~"))//2["","123",]}funcprintStrs(s[]string){fmt.Print(len(s),"[")for_,item:=ranges{fmt.Printf(`"%s",`,item)}fmt.Print("]\n")}执行结果Scala然后,我再次尝试了Scala,发现在Scala中,split的行为有点不同。"".split("").length"~".split("~").length"~~".split("~").length"".split("~").length"~123".split("~").lengthcode"~".split("~")返回一个空数组,和我们在Java中遇到的问题一模一样。Java最后,我在Java包example;importorg.junit.Test;publicclassExampleTest{@TestpublicvoidtestSplit(){printStrings("".split(""));中执行了相同的代码。//1["",]printStrings("~".split("~"));//0[]printStrings("~~".split("~"));//0[]printStrings("".split("~"));//1["",]printStrings("~123".split("~"));//2["","123",]}privatevoidprintStrings(String[]strings){System.out.print(strings.length+"[");for(Stringstr:strings){System.out.printf("\"%s\",",str);}System.out.println("]");}}执行结果和Scala一致,也解释了为什么会遇到ArrayIndexOutOfBoundsException的问题。之所以看了JavaAPI文档,发现Java中的split方法确实和其他语言不一样。这一点特别容易被忽视。如果分隔符表达式与字符串不匹配,则原始字符串将作为数组中的唯一一个返回。值,解释"".split("")//1[""]"".split("~")//1[""]如果分隔符形式匹配字符串的起始字符已经匹配,则返回值中第一个元素会被设置为"""~123".split("~")//2["","123"]如果limit参数为0,即split(Stringregex)方法,匹配结果末尾的所有空字符串""都会被丢弃,这就解释了下面两段代码"~".split("~")//0[]"~~".split("~")//0[]然后看了Scala的官方文档,Scala和Java的行为是一致的。综上所述,Java中使用字符串的split方法,大体上和其他编程语言的表现是一样的,只是在某些边界条件下,存在一些不一致的地方。这是我们应该注意的,这也提醒我们不要想当然地认为不同语言中同名的函数(方法)具有完全相同的功能。当我们遇到一些奇怪的问题时,多看官方文档才是硬道理。本文将不断修订更新。最新内容可以参考我的GITHUB上的程序员成长计划项目。欢迎来到星空。更多精彩内容,请关注我。