Answers:
他们本质上是课程的马。
Scanner
是为需要解析字符串,提取不同类型数据的情况而设计的。它非常灵活,但是可以说它并没有给您提供最简单的API来简单地获取由特定表达式分隔的字符串数组。String.split()
并Pattern.split()
为您提供了执行后者的简单语法,但这实际上就是他们所做的全部。如果您想解析结果字符串,或者中途根据特定标记更改定界符,它们将无济于事。StringTokenizer
比更具限制性String.split()
,并且使用起来也比较麻烦。它本质上是为提取由固定子字符串分隔的令牌而设计的。由于此限制,速度约为的两倍String.split()
。(请参阅与String.split()
和的比较StringTokenizer
。)它也早于正则表达式API,而正则表达式API是其中String.split()
的一部分。您会从我的计时中注意到,在典型的机器上,String.split()
它仍然可以在几毫秒内标记成千上万个字符串。此外,它的优点还StringTokenizer
在于它可以将输出作为字符串数组提供,这通常是您想要的。在大多数情况下,使用Enumeration
所提供的StringTokenizer
,在语法上过于挑剔。从这个角度来看,StringTokenizer
如今有点浪费空间,您最好只使用String.split()
。
StringTokenizer
我的最佳选择,因为String.split()
这只会耗尽内存吗?
让我们从消除开始StringTokenizer
。它越来越老了,甚至不支持正则表达式。其文档指出:
StringTokenizer
是一个旧类,出于兼容性原因而保留,尽管在新代码中不鼓励使用它。建议任何寻求此功能的人改用或的split
方法。String
java.util.regex
因此,让我们立即将其丢弃。这树叶split()
和Scanner
。它们之间有什么区别?
一方面,split()
只需返回一个数组,这使得使用foreach循环变得容易:
for (String token : input.split("\\s+") { ... }
Scanner
建立起来更像是流:
while (myScanner.hasNext()) {
String token = myScanner.next();
...
}
要么
while (myScanner.hasNextDouble()) {
double token = myScanner.nextDouble();
...
}
(它具有相当大的API,因此不要以为它总是局限于如此简单的东西。)
当您在开始解析之前没有(或无法获得)所有输入时,此流样式的界面对于解析简单的文本文件或控制台输入很有用。
就我个人而言,我唯一记得的Scanner
是用于学校项目,当时我必须从命令行获取用户输入。它使这种操作变得容易。但是,如果我有一个String
要拆分的产品,可以轻松选择split()
。
Scanner
检测给定中的换行符String
。由于平台之间的换行符可能会有所不同(请Pattern
参见javadoc!),并且不能保证输入字符串符合要求System.lineSeparator()
,我发现Scanner
它更合适,因为它已经知道调用时要寻找的换行符nextLine()
。因为String.split
我将必须输入正确的正则表达式模式来检测行分隔符,但我发现行分隔符没有存储在任何标准位置(我能做的最好的事情是从Scanner
类的源代码中复制它)。
StringTokenizer一直在那儿。它是最快的,但是类似枚举的习惯用法可能看起来不那么优雅。
拆分在JDK 1.4上存在。比tokenizer慢,但更易于使用,因为它可以从String类调用。
扫描仪开始使用JDK 1.5。它是最灵活的,并且填补了Java API的长期空白,以支持等效的著名Cs scanf函数家族。
如果您有要标记的String对象,则优先使用String的split方法而不是StringTokenizer。如果您要从程序外部的源(例如文件或用户)中解析文本数据,则可以使用Scanner。
分割速度很慢,但没有Scanner慢。StringTokenizer比拆分快。但是,我发现我可以通过交易一些灵活性来获得双倍的速度来获得速度提升,这是我在JFastParser https://github.com/hughperkins/jfastparser所做的
在包含一百万双的字符串上进行测试:
Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms
String.split似乎比StringTokenizer慢得多。split的唯一好处是您可以获得令牌数组。您也可以在split中使用任何正则表达式。org.apache.commons.lang.StringUtils具有split方法,该方法比两个方法中的任何一个都快得多。StringTokenizer或String.split。但是这三者的CPU利用率几乎相同。因此,我们还需要一种不占用大量CPU的方法,但我仍然找不到。
我最近做了一些实验,研究在性能敏感的情况下String.split()的性能不好。您可能会发现这很有用。
http://eblog.chrononsystems.com/hidden-evils-of-javas-stringsplit-and-stringr
要点是,与使用预编译的Pattern对象并将其直接用于String进行操作相比,String.split()每次都会编译正则表达式模式,因此会降低程序速度。
对于默认方案,我也建议您使用Pattern.split(),但是如果您需要最大的性能(尤其是在Android上,我测试的所有解决方案都非常慢),并且只需要分割一个字符,我现在使用我自己的方法:
public static ArrayList<String> splitBySingleChar(final char[] s,
final char splitChar) {
final ArrayList<String> result = new ArrayList<String>();
final int length = s.length;
int offset = 0;
int count = 0;
for (int i = 0; i < length; i++) {
if (s[i] == splitChar) {
if (count > 0) {
result.add(new String(s, offset, count));
}
offset = i + 1;
count = 0;
} else {
count++;
}
}
if (count > 0) {
result.add(new String(s, offset, count));
}
return result;
}
使用“ abc” .toCharArray()获取字符串的char数组。例如:
String s = " a bb ccc dddd eeeee ffffff ggggggg ";
ArrayList<String> result = splitBySingleChar(s.toCharArray(), ' ');
一个重要的区别是String.split()和Scanner都可以生成空字符串,但StringTokenizer却不会这样做。
例如:
String str = "ab cd ef";
StringTokenizer st = new StringTokenizer(str, " ");
for (int i = 0; st.hasMoreTokens(); i++) System.out.println("#" + i + ": " + st.nextToken());
String[] split = str.split(" ");
for (int i = 0; i < split.length; i++) System.out.println("#" + i + ": " + split[i]);
Scanner sc = new Scanner(str).useDelimiter(" ");
for (int i = 0; sc.hasNext(); i++) System.out.println("#" + i + ": " + sc.next());
输出:
//StringTokenizer
#0: ab
#1: cd
#2: ef
//String.split()
#0: ab
#1: cd
#2:
#3: ef
//Scanner
#0: ab
#1: cd
#2:
#3: ef
这是因为String.split()和Scanner.useDelimiter()的分隔符不仅是字符串,而且是正则表达式。在上面的示例中,我们可以将定界符“”替换为“ +”,使它们的行为类似于StringTokenizer。
String.split()可以很好地工作,但是具有自己的边界,例如,如果您想根据单或双竖线(|)符号如下所示拆分字符串,则该字符串将不起作用。在这种情况下,您可以使用StringTokenizer。
ABC | IJK