Scanner 与 StringTokenizer 与 String.Split

我刚刚了解了 Java 的 Scanner 类,现在我想知道它是如何与 StringTokenizer 和 String 进行比较/竞争的。分开。我知道 StringTokenizer 和 String。拆分只能在字符串上工作,那么为什么我要使用字符串扫描器呢?扫描仪是否只是为了一站式分割服务?

152464 次浏览

它们基本上就是赛马。

  • Scanner是为需要解析字符串、提取不同类型的数据而设计的。它非常灵活,但可以说并不能提供最简单的 API 来获得由特定表达式分隔的字符串数组。
  • String.split()Pattern.split()为后者提供了一种简单的语法,但这基本上就是它们所做的全部工作。如果您想要解析结果字符串,或者根据特定的标记在中间更改分隔符,它们不会帮助您。
  • StringTokenizer甚至比 String.split()更具限制性,而且使用起来也有点费劲。它本质上是为提取由固定子字符串分隔的标记而设计的。由于这个限制,它的速度大约是 String.split()的两倍。(见我的 ABC1与 StringTokenizer的比较。)它还早于正则表达式 API,String.split()是其中的一部分。

您将从我的计时中注意到,String.split()仍然可以在一台典型的机器上对 在几毫秒内产生数千个字符串进行标记化。此外,与 StringTokenizer相比,它还有一个优点,即它以字符串数组的形式提供输出,这通常是您想要的。使用由 StringTokenizer提供的 Enumeration,在大多数情况下“语法上过于繁琐”。从这个角度来看,StringTokenizer现在有点浪费空间,您不妨使用 String.split()

如果您有一个要标记的 String 对象,请使用 String 的 分开方法而不是 StringTokenizer。如果您正在解析来自程序之外的源的文本数据,比如来自文件或来自用户的文本数据,那么就需要使用扫描器了。

StringTokenizer 一直在那里。它是最快的,但是类枚举的习惯用法可能看起来不像其他习惯用法那样优雅。

拆分在 JDK 1.4上出现。比 tokenizer 慢,但更容易使用,因为它可以从 String 类调用。

JDK 1.5上出现了扫描器。它是最灵活的,并且填补了 JavaAPI 上长期存在的空白,以支持与著名的 Cs 扫描函数家族等价的函数。

让我们从消除 StringTokenizer开始。它已经过时了,甚至不支持正则表达式。它的文档说明:

StringTokenizer是出于兼容性原因而保留的遗留类,尽管在新代码中不鼓励使用它。建议任何寻求此功能的人使用 Stringsplit方法或 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();
...
}

(它有一个相当 大型空气污染指数,所以不要认为它总是局限于这么简单的事情。)

当您在开始解析之前没有(或无法获得)所有输入时,这个流样式的接口对于解析简单的文本文件或控制台输入非常有用。

就个人而言,我唯一记得使用 Scanner的时候是在学校项目中,那时我必须从命令行获得用户输入。这使得这种操作变得容易。但是,如果我有一个 String,我想分开,它几乎是一个不用思考就可以与 split()

Split 似乎比 StringTokenizer 慢得多。分割的唯一好处是您可以得到一个令牌数组。还可以在拆分中使用任何正则表达式。 StringUtils 有一个分割方法,它比 StringTokenizer 或 String.split 这两个方法中的任何一个都要快得多。 但是三者的 CPU 利用率几乎相同。因此,我们还需要一个 CPU 密集程度较低的方法,但我仍然无法找到这种方法。

我最近做了一些关于 String.split ()在性能高度敏感的情况下性能不佳的实验。你会发现这个很有用。

Java 的 String.split ()和 place ()的隐患

要点是,String.split ()每次都会编译一个正则表达式模式,因此与使用预编译的 Pattern 对象并直接使用它对 String 进行操作相比,它可以降低程序的速度。

拆分很慢,但不如扫描仪慢。StringTokenizer 比 Split 快。然而,我发现我可以获得两倍的速度,通过交易一些灵活性,得到一个速度提高,我在 JFastParserhttps://github.com/hughperkins/jfastparser

测试一根包含一百万个双打的绳子:

Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms

Split ()工作得非常好,但是有自己的边界,比如如果您希望基于单管道符号或双管道符号拆分下面所示的字符串,那么它就不能工作。在这种情况下,您可以使用 StringTokenizer。

ABC | IJK

对于默认场景,我也建议使用 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 ()和 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 ()和 Scaner.useDeliiter ()的分隔符不仅是一个字符串,而且是一个正则表达式。我们可以在上面的示例中将分隔符“”替换为“ +”,使它们的行为类似于 StringTokenizer。