扫描器与StringTokenizer与String.Split


155

我刚刚了解了Java的Scanner类,现在我想知道它如何与StringTokenizer和String.Split进行比较/竞争。我知道StringTokenizer和String.Split仅适用于字符串,那么为什么要对字符串使用扫描器?扫描仪是否仅打算一站式进行拆分?

Answers:


240

他们本质上是课程的马。

  • 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()


8
在与您在String.Split和StringTokenizer上运行的相同测试中查看Scanner的结果也将很有趣。
戴夫

2
给了我另一个问题的答案:“为什么不鼓励使用StringTokenizer,如Java API注释中所述?”。从本文看来,答案似乎是“因为String.split()足够快”。

1
那么StringTokenizer现在是否已被弃用?
史蒂夫制造者

用什么代替它?扫描器?
阿德里安

4
我意识到这是一个古老问题的答案,但是如果我需要即时将庞大的文本流拆分为令牌,那不是StringTokenizer我的最佳选择,因为String.split()这只会耗尽内存吗?
谢尔盖·塔切诺夫'16

57

让我们从消除开始StringTokenizer。它越来越老了,甚至不支持正则表达式。其文档指出:

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


20
StringTokenizer的速度是String.split()的2倍。如果您不需要使用正则表达式,那就不要!
亚历克斯·沃登

我只是用来Scanner检测给定中的换行符String。由于平台之间的换行符可能会有所不同(请Pattern参见javadoc!),并且不能保证输入字符串符合要求System.lineSeparator(),我发现Scanner它更合适,因为它已经知道调用时要寻找的换行符nextLine()。因为String.split我将必须输入正确的正则表达式模式来检测行分隔符,但我发现行分隔符没有存储在任何标准位置(我能做的最好的事情是从Scanner类的源代码中复制它)。
ADTC

9

StringTokenizer一直在那儿。它是最快的,但是类似枚举的习惯用法可能看起来不那么优雅。

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

扫描仪开始使用JDK 1.5。它是最灵活的,并且填补了Java API的长期空白,以支持等效的著名Cs scanf函数家族。


6

如果您有要标记的String对象,则优先使用String的split方法而不是StringTokenizer。如果您要从程序外部的源(例如文件或用户)中解析文本数据,则可以使用Scanner。


5
这样,没有理由,没有理由吗?
jan.supol '16

6

分割速度很慢,但没有Scanner慢。StringTokenizer比拆分快。但是,我发现我可以通过交易一些灵活性来获得双倍的速度来获得速度提升,这是我在JFastParser https://github.com/hughperkins/jfastparser所做的

在包含一百万双的字符串上进行测试:

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

一些Javadoc会很好,如果您想解析数字数据以外的内容怎么办?
NickJ 2013年

好吧,它是为提高速度而不是美观而设计的。这很简单,只有几行,因此您可以根据需要添加更多选项进行文本解析。
休·珀金斯

4

String.split似乎比StringTokenizer慢得多。split的唯一好处是您可以获得令牌数组。您也可以在split中使用任何正则表达式。org.apache.commons.lang.StringUtils具有split方法,该方法比两个方法中的任何一个都快得多。StringTokenizer或String.split。但是这三者的CPU利用率几乎相同。因此,我们还需要一种不占用大量CPU的方法,但我仍然找不到。


3
这个答案有点荒谬。您说您正在寻找更快但“占用较少CPU”的东西。任何程序均由CPU执行。如果某个程序没有使用100%的CPU,则它必须在等待其他东西,例如I / O。在讨论字符串标记化时,这永远不会成为问题,除非您要进行直接磁盘访问(我们在这里显然没有这样做)。
Jolta 2013年

4

我最近做了一些实验,研究在性能敏感的情况下String.split()的性能不好。您可能会发现这很有用。

http://eblog.chrononsystems.com/hidden-evils-of-javas-stringsplit-and-stringr

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


4
实际上,String.split()并不总是编译模式。看一下1.7 java的源代码,您会看到是否检查了模式是否为单个字符而不是转义的字符,它将分割字符串而不使用regexp,因此它应该非常快。
KrzysztofKrasoń2012年

1

对于默认方案,我也建议您使用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(), ' ');

1

一个重要的区别是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。


-5

String.split()可以很好地工作,但是具有自己的边界,例如,如果您想根据单或双竖线(|)符号如下所示拆分字符串,则该字符串将不起作用。在这种情况下,您可以使用StringTokenizer。

ABC | IJK


12
实际上,您可以仅使用“ ABC | IJK” .split(“ \\ |”);拆分示例。
Tomo

尽管“ ABC || DEF ||” .split(“ \\ |”)并不会真正起作用,因为它将忽略尾随的两个空值,这会使解析变得比应有的更为复杂。
Armand 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.