为什么大多数基于正则expression式的Javastring操作?

在Java中,有许多方法都是与操纵string有关的。 最简单的例子是String.split(“something”)方法。

现在许多这些方法的实际定义是它们都以正则expression式作为其input参数。 这使得所有非常强大的构build块。

现在有两种方法可以在许多方法中看到:

  1. 每次调用方法时都会重新编译expression式。 因此,他们施加了性能影响。
  2. 我发现,在大多数“现实生活”的情况下,这些方法被称为“固定”的文本。 split方法最常见的用法更糟糕:通常用单个字符(通常是“,”,“或”&“)来分隔。

所以这不仅是默认的方法是强大的,他们似乎也被压倒了实际使用的东西。 在内部,我们已经开发了一个“固定string”的“快速分裂”方法。 我在家里写了一个testing,看看如果知道它是一个字符,我能做多快。 两者都比“标准”拆分方法快得多。

所以我想知道:为什么现在selectJava API? 有什么好的理由,而不是像split(char)和split(String)和splitRegex(String)?


更新:我打了几个电话,看看分割一个string的各种方式需要多less时间。

简短的总结:这是一个很大的区别!

我为每个testing用例做了10000000次迭代,总是使用input

"aap,noot,mies,wim,zus,jet,teun" 

并始终使用“,”或“,”作为拆分参数。

这是我的Linux系统(这是一个Atom D510盒,所以它有点慢):

 fastSplit STRING Test 1 : 11405 milliseconds: Split in several pieces Test 2 : 3018 milliseconds: Split in 2 pieces Test 3 : 4396 milliseconds: Split in 3 pieces homegrown fast splitter based on char Test 4 : 9076 milliseconds: Split in several pieces Test 5 : 2024 milliseconds: Split in 2 pieces Test 6 : 2924 milliseconds: Split in 3 pieces homegrown splitter based on char that always splits in 2 pieces Test 7 : 1230 milliseconds: Split in 2 pieces String.split(regex) Test 8 : 32913 milliseconds: Split in several pieces Test 9 : 30072 milliseconds: Split in 2 pieces Test 10 : 31278 milliseconds: Split in 3 pieces String.split(regex) using precompiled Pattern Test 11 : 26138 milliseconds: Split in several pieces Test 12 : 23612 milliseconds: Split in 2 pieces Test 13 : 24654 milliseconds: Split in 3 pieces StringTokenizer Test 14 : 27616 milliseconds: Split in several pieces Test 15 : 28121 milliseconds: Split in 2 pieces Test 16 : 27739 milliseconds: Split in 3 pieces 

正如你所看到的,如果你有很多“固定字符”分裂的话,这会有很大的不同。

给你们一些见解; 目前我正在Apache日志文件和Hadoop竞技场中使用网站的数据。 所以对我来说这个东西真的很重要:)

我没有考虑到的东西是垃圾收集器。 据我可以告诉编译正则expression式到一个模式/匹配/ ..将分配很多的对象,需要收集一些时间。 所以也许从长远来看,这些版本之间的差异甚至更大或更小。

我迄今的结论是:

  • 如果你有很多的string需要分割,只需要优化它。
  • 如果您使用正则expression式方法总是预编译,如果您重复使用相同的模式。
  • 忘掉(过时的)StringTokenizer
  • 如果你想分割一个字符,然后使用自定义的方法,特别是如果你只需要将其分割成特定数量的块(如… 2)。

PS我给你所有我自己的分裂的字符方法玩(根据许可证,这个网站上的所有东西:))。 我从来没有完全testing他们..但。 玩的开心。

 private static String[] stringSplitChar(final String input, final char separator) { int pieces = 0; // First we count how many pieces we will need to store ( = separators + 1 ) int position = 0; do { pieces++; position = input.indexOf(separator, position + 1); } while (position != -1); // Then we allocate memory final String[] result = new String[pieces]; // And start cutting and copying the pieces. int previousposition = 0; int currentposition = input.indexOf(separator); int piece = 0; final int lastpiece = pieces - 1; while (piece < lastpiece) { result[piece++] = input.substring(previousposition, currentposition); previousposition = currentposition + 1; currentposition = input.indexOf(separator, previousposition); } result[piece] = input.substring(previousposition); return result; } private static String[] stringSplitChar(final String input, final char separator, final int maxpieces) { if (maxpieces <= 0) { return stringSplitChar(input, separator); } int pieces = maxpieces; // Then we allocate memory final String[] result = new String[pieces]; // And start cutting and copying the pieces. int previousposition = 0; int currentposition = input.indexOf(separator); int piece = 0; final int lastpiece = pieces - 1; while (currentposition != -1 && piece < lastpiece) { result[piece++] = input.substring(previousposition, currentposition); previousposition = currentposition + 1; currentposition = input.indexOf(separator, previousposition); } result[piece] = input.substring(previousposition); // All remaining array elements are uninitialized and assumed to be null return result; } private static String[] stringChop(final String input, final char separator) { String[] result; // Find the separator. final int separatorIndex = input.indexOf(separator); if (separatorIndex == -1) { result = new String[1]; result[0] = input; } else { result = new String[2]; result[0] = input.substring(0, separatorIndex); result[1] = input.substring(separatorIndex + 1); } return result; } 

请注意,正则expression式不需要每次重新编译。 从Javadoc :

str.split(regex, n)forms的这种方法的调用产生与expression式相同的结果

 Pattern.compile(regex).split(str, n) 

也就是说,如果你担心性能,你可以预编译模式,然后重用:

 Pattern p = Pattern.compile(regex); ... String[] tokens1 = p.split(str1); String[] tokens2 = p.split(str2); ... 

代替

 String[] tokens1 = str1.split(regex); String[] tokens2 = str2.split(regex); ... 

我相信这个APIdevise的主要原因是方便。 由于正则expression式也包含了所有“固定”的string/字符,所以它简化了API来使用一个方法而不是几个方法。 如果有人担心性能,正则expression式仍然可以如上所示进行预编译。

我的感觉(我不能回溯任何统计证据)是大多数情况下String.split()用于性能不是问题的上下文中。 例如,这是一次性的行为,或者与其他因素相比,性能差异可以忽略不计。 国际海事组织罕见的情况下,你在一个紧密的循环中使用相同的正则expression式数千次拆分string,性能优化确实是有道理的。

看到一个正则expression式匹配器实现与固定的string/字符相比,专门为这些匹配器的性能比较将是有趣的。 这个差别可能不够大,不足以certificate单独的实现。

我不会说大多数string操作是基于正则expression式的Java。 真的,我们只是在谈论splitreplaceAll / replaceFirst 。 但我同意,这是一个很大的错误。

除了低级语言特征(string)依赖于更高级特性(正则expression式)的丑陋之外,对于新用户来说,这也是一个令人讨厌的陷阱,他们可能自然地认为具有签名String.replaceAll(String, String)将是一个stringreplace函数。 在这个假设下编写的代码看起来就像是在工作,直到一个正则expression式的特殊字符出现,在这一点上,你会感到困惑,难以debugging(甚至是安全意义上的)错误。

有趣的是,一种对于打字很严格的语言使得把一个string和一个正则expression式当成是一个马虎的错误是一回事。 没有内build的方法来做一个简单的stringreplace或拆分,这是不太有趣的。 你必须用一个Pattern.quote dstring来replace正则expression式。 而且你甚至只能从Java 5开始。 绝望。

@Tim Pietzcker:

还有其他的语言也一样吗?

JavaScript的string部分是模仿Java的,在replace()的情况下也是混乱的。 通过传递一个string,你可以得到一个简单的stringreplace,但是它只replace了第一个匹配,这是很less需要的。 要得到一个replace – 你必须通过/g标志传入一个RegExp对象,如果你想从一个stringdynamic地创build它(在JS中没有内置的RegExp.quote方法),这RegExp.quote 。 幸运的是, split()纯粹是基于string的,所以你可以使用这个习惯用法:

 s.split(findstr).join(replacestr) 

当然Perl还会用regexen来做所有的事情,因为它就是这样的。

(这不仅仅是一个答案,而是一个评论,但是它太大了, 为什么 Java这么做?不知道,他们在早期犯了很多错误,其中一些已经被修复了,我怀疑他们是否会被认为把正则expression式的function放在标记为1.0的String的框中, String的devise将更加清晰。

我想一个很好的理由是,他们可以简单地把这个压力传递给正则expression式方法,这对所有的string方法来说都是非常重要的。 我猜测他们认为,如果他们已经有了一个可行的解决scheme,那么从开发和维护的angular度来看,效率会比较低,为每个string操作方法重新发明了方向。

有趣的讨论!

Java本来不是作为批处理编程语言来使用的。 因此,除了在应用程序初始化时应用程序可能被分析一堆configuration文件之外,开箱即用的API更多地被调整为“replace”,“parsing”等。

因此,这些API的优化在IMO简单的祭坛中被牺牲了。 但这个问题提出了一个重要的观点。 Python在其API中保持与非正则expression式截然不同的愿望源于Python也可以用作优秀的脚本语言。 在UNIX中,fgrep的原始版本也不支持正则expression式。

我参与了一个项目,我们不得不在java中进行一些ETL工作。 那个时候,我想起你提到的那种你提到的那种优化。

我怀疑为什么像String#split(String)使用regexp的原因是因为它在Java类库中涉及更less的无关代码。 状态机由于诸如空间或空间之类的分裂而产生的结果非常简单,以至于不太可能比使用StringCharacterIterator的静态实现的等价物执行速度慢得多

除此之外,静态实现的解决scheme将使运行时优化与JIT复杂化,因为它将是一个不同的代码块,也需要热代码分析。 在库中定期使用现有的Patternalgorithm意味着它们更有可能成为JIT编译的候选对象。

非常好的问题

我想,当devise师坐下来看这个(而不是很长时间,看起来),他们从一个angular度来看,它应该被devise为尽可能多的不同的可能性。 正则expression式提供了这种灵活性。

他们没有考虑效率。 有Java社区过程可用来提出这一点。

你有没有看过使用java.util.regex.Pattern类,在那里你编译expression式,然后在不同的string上使用。

 Pattern exp = Pattern.compile(":"); String[] array = exp.split(sourceString1); String[] array2 = exp.split(sourceString2); 

在查看Javastring类,正则expression式的使用看起来是合理的,有不同的正则expression式是不需要的:

http://java.sun.com/javase/6/docs/api/java/lang/String.html

boolean matches(String regex) – 一个正则expression式似乎是适当的,否则你可以使用equals

String replaceAll/replaceFirst(String regex, String replacement) – 有相当于采取CharSequence,而不是正则expression式。

String[] split(String regex, int limit) – 一个强大但昂贵的拆分,可以使用StringTokenizer通过标记分割。

这些是我看到的正则expression式的唯一function。

编辑:在看到StringTokenizer是遗留的,我会推迟到PéterTörök的预编译的正则expression式,而不是使用标记器的答案。

你的问题的答案是Java核心API做错了。 对于日常工作,您可以考虑使用Guava图书馆的CharMatcher,它可以很好地填补空白。

…为什么现在selectJava API?

简短的回答:不是。 没有人决定在String API中使用正则expression式方法而非正则expression式方法,只是这样做了。

我总是明白,Java的devise者故意将string操作方法保持在最低限度,以避免API膨胀。 但是,当在JDK 1.4中出现正则expression式支持时,他们不得不为String的API添加一些便利方法。

所以,现在用户面临着非常强大和灵活的正则expression式方法和Java总是提供的骨骼基本方法之间的select。