我刚刚了解了 Java 的 Scanner 类,现在我想知道它如何与 StringTokenizer 和 String.Split 进行比较/竞争。我知道 StringTokenizer 和 String.Split 仅适用于字符串,那么我为什么要使用 Scanner 处理字符串呢?Scanner 是否只是为拆分提供一站式服务?
10 回答
它们本质上是课程的马。
Scanner
专为需要解析字符串、提取不同类型数据的情况而设计。它非常灵活,但可以说没有为您提供最简单的 API 来简单地获取由特定表达式分隔的字符串数组。String.split()
并Pattern.split()
为您提供执行后者的简单语法,但这基本上就是他们所做的一切。如果您想解析生成的字符串,或者根据特定标记在中途更改分隔符,它们将无助于您。StringTokenizer
比 更具限制性String.split()
,而且使用起来也有点复杂。它本质上是为提取由固定子字符串分隔的标记而设计的。由于这个限制,它的速度大约是String.split()
. (参见我对和的比较String.split()
StringTokenizer
。)它还早于正则表达式 API,后者String.split()
是其中的一部分。
您会从我的计时中注意到,在典型机器上仍然可以在几毫秒内String.split()
标记数千个字符串。此外,它的优势在于它将输出作为字符串数组提供给您,这通常是您想要的。大多数情况下,使用由 提供的过于“语法上的挑剔”。从这个角度来看,现在有点浪费空间,你还不如直接使用.StringTokenizer
Enumeration
StringTokenizer
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()
。
StringTokenizer 一直都在。它是最快的,但类似枚举的习语可能看起来不像其他习语那么优雅。
split 在 JDK 1.4 上出现。比标记器慢但更易于使用,因为它可以从 String 类中调用。
Scanner 出现在 JDK 1.5 上。它是最灵活的,并且填补了 Java API 中长期存在的空白,以支持与著名的 Cs scanf 函数系列等效的功能。
Split 很慢,但没有 Scanner 慢。StringTokenizer 比拆分更快。然而,我发现我可以通过交易一些灵活性来获得双倍的速度,以获得速度提升,这是我在 JFastParser https://github.com/hughperkins/jfastparser所做的
对包含一百万个双精度的字符串进行测试:
Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms
如果你有一个要标记的 String 对象,请优先使用 String 的split方法而不是 StringTokenizer。如果您要从程序外部的源(例如文件或用户)解析文本数据,那么扫描器就派上用场了。
String.split 似乎比 StringTokenizer 慢得多。split 的唯一优点是您可以获得一个令牌数组。您也可以在拆分中使用任何正则表达式。org.apache.commons.lang.StringUtils 有一个 split 方法,它比两个中的任何一个都快得多。StringTokenizer 或 String.split。但三者的 CPU 利用率几乎相同。因此,我们还需要一种 CPU 密集度较低的方法,但我仍然找不到。
我最近做了一些关于 String.split() 在高性能敏感情况下性能不佳的实验。您可能会发现这很有用。
Java 的 String.split() 和 replace() 隐藏的弊端
要点是 String.split() 每次都会编译正则表达式模式,因此与使用预编译的 Pattern 对象并直接使用它来操作字符串相比,它会减慢程序的速度。
一个重要的区别是 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。
对于默认情况,我也建议使用 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() 获取字符串的字符数组。例如:
String s = " a bb ccc dddd eeeee ffffff ggggggg ";
ArrayList<String> result = splitBySingleChar(s.toCharArray(), ' ');
String.split() 效果很好,但有自己的界限,比如如果你想根据单管道或双管道 (|) 符号拆分字符串,如下所示,它不起作用。在这种情况下,您可以使用 StringTokenizer。
ABC|IJK