Scanner 与 StringTokenizer 与 String.Split

Scanner vs. StringTokenizer vs. String.Split

提问人:Dave 提问时间:3/28/2009 最后编辑:skaffmanDave 更新时间:12/27/2021 访问量:154573

问:

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

java.util.scanner 标记化 拆分

评论


答:

249赞 Neil Coffey 3/28/2009 #1

他们本质上是课程的马。

  • Scanner专为需要解析字符串、提取不同类型的数据的情况而设计。它非常灵活,但可以说没有为您提供最简单的 API,用于简单地获取由特定表达式分隔的字符串数组。
  • String.split()并给你一个简单的语法来做后者,但这基本上就是他们所做的一切。如果您想解析生成的字符串,或者根据特定令牌在中途更改分隔符,它们将无济于事。Pattern.split()
  • StringTokenizer甚至比 更严格,而且使用起来也有点繁琐。它本质上是为拉出由固定子字符串分隔的令牌而设计的。由于此限制,它的速度大约是 的两倍。(请参阅我对 String.split()StringTokenizer 的比较。它也早于正则表达式 API,后者是其中的一部分。String.split()String.split()String.split()

你会从我的计时中注意到,在典型的机器上,仍然可以在几毫秒内标记数千个字符串。此外,它的优势在于它以字符串数组的形式为您提供输出,这通常是您想要的。大多数时候,使用 提供的 是 太“语法上挑剔”了。从这个角度来看,现在有点浪费空间,还不如直接使用。String.split()StringTokenizerEnumerationStringTokenizerStringTokenizerString.split()

评论

8赞 Dave 3/28/2009
看看 Scanner 在 String.Split 和 StringTokenizer 上运行的相同测试的结果也会很有趣。
2赞 Legs 5/1/2011
给了我另一个问题的答案:“为什么不鼓励使用 StringTokenizer,如 Java API 说明中所述?从这段文字来看,答案似乎是“因为 String.split() 足够快”。
2赞 Steve the Maker 3/6/2012
那么 StringTokenizer 现在几乎被弃用了吗?
0赞 Adrian 7/30/2014
用什么代替它?扫描器?
4赞 Sergei Tachenov 1/26/2016
我意识到这是一个老问题的答案,但是如果我需要即时将一个巨大的文本流拆分为令牌,这难道不是我最好的选择吗,因为只会耗尽内存?StringTokenizerString.split()
6赞 Bill the Lizard 3/28/2009 #2

如果要标记化 String 对象,请使用 String 的 split 方法而不是 StringTokenizer。如果您要解析来自程序外部源(例如文件或用户)的文本数据,那么 Scanner 就派上用场了。

评论

5赞 jan.supol 6/29/2016
就这样,没有理由,没有理由?
9赞 HMM 3/28/2009 #3

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

split 在 JDK 1.4 上出现。比分词器慢,但更易于使用,因为它可以从 String 类中调用。

Scanner 出现在 JDK 1.5 上。它是最灵活的,填补了 Java API 上长期存在的空白,以支持著名的 Cs scanf 函数系列的等效功能。

58赞 Michael Myers 3/28/2009 #4

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

StringTokenizer是出于兼容性原因而保留的旧类,尽管不鼓励在新代码中使用该类。建议任何寻求此功能的人都改用 或 包的方法。splitStringjava.util.regex

所以让我们马上把它扔掉。剩下的就是 split()Scanner。它们之间有什么区别?

首先,只需返回一个数组,这样可以很容易地使用 foreach 循环:split()

for (String token : input.split("\\s+") { ... }

Scanner构建得更像一个流:

while (myScanner.hasNext()) {
    String token = myScanner.next();
    ...
}

while (myScanner.hasNextDouble()) {
    double token = myScanner.nextDouble();
    ...
}

(它有一个相当大的 API,所以不要以为它总是局限于这么简单的东西。

此流样式接口可用于分析简单的文本文件或控制台输入,当您在开始分析之前没有(或无法获取)所有输入时。

就我个人而言,我唯一记得使用的时间是用于学校项目,当时我必须从命令行获取用户输入。它使这种操作变得容易。但是,如果我有一个我想分手的人,那几乎是不费吹灰之力的。ScannerStringsplit()

评论

24赞 Alex Worden 1/9/2013
StringTokenizer 的速度是 String.split() 的 2 倍。如果您不需要使用正则表达式,请不要使用正则表达式!
0赞 ADTC 8/16/2013
我只是用来检测给定 .由于换行符可能因平台而异(看看 javadoc!并且输入字符串不能保证符合,我发现更合适,因为它在调用时已经知道要查找哪些换行符。因为我必须输入正确的正则表达式模式来检测行分隔符,我发现它没有存储在任何标准位置(我能做的最好的事情就是从类的源代码中复制它)。ScannerStringPatternSystem.lineSeparator()ScannernextLine()String.splitScanner
4赞 Manish 11/5/2009 #5

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

评论

3赞 Jolta 1/4/2013
这个答案有点荒谬。你说你正在寻找更快但“CPU密集度更低”的东西。任何程序都由 CPU 执行。如果一个程序没有 100% 利用你的 CPU,那么它一定在等待其他东西,比如 I/O。在讨论字符串标记化时,这应该不是问题,除非您正在进行直接光盘访问(我们在这里没有这样做)。
4赞 pdeva 1/26/2012 #6

我最近做了一些实验,关于 String.split() 在高度性能敏感的情况下的糟糕性能。您可能会发现这很有用。

Java 的 String.split() 和 replace() 隐藏的弊端

要点是 String.split() 每次都会编译一个正则表达式模式,因此会减慢程序的速度,而使用预编译的 Pattern 对象并直接使用它来操作 String。

评论

4赞 Krzysztof Krasoń 11/8/2012
实际上,String.split() 并不总是编译模式。如果是 1.7 java,看看源代码,你会看到有一个检查模式是否是单个字符而不是转义的,它会在没有正则表达式的情况下拆分字符串,所以它应该相当快。
7赞 Hugh Perkins 10/11/2012 #7

Split 很慢,但不像 Scanner 那么慢。StringTokenizer 比 split 更快。但是,我发现我可以通过交换一些灵活性来获得双倍的速度,从而获得速度提升,我在 JFastParser https://github.com/hughperkins/jfastparser 中做到了这一点

在包含 100 万个双精度的字符串上进行测试:

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

评论

0赞 NickJ 4/9/2013
一些 Javadoc 会很好,如果您想解析数字数据以外的其他内容怎么办?
0赞 Hugh Perkins 4/15/2013
嗯,它是为速度而设计的,而不是为美观而设计的。这很简单,只有几行,所以如果你愿意,你可以添加更多的文本解析选项。
-5赞 Mujahid shaik 2/23/2013 #8

String.split() 工作得很好,但有自己的边界,比如如果你想根据单管或双管 (|) 符号拆分如下图所示的字符串,它不起作用。在这种情况下,可以使用 StringTokenizer。

美国广播公司|艾克

评论

13赞 Tomo 2/23/2013
实际上,您可以仅使用“ABC|IJK“.split(”\\|“);
0赞 Armand 8/5/2014
“ABC||DEF||”。split(“\\|”) 并没有真正起作用,因为它会忽略尾随的两个空值,这使得解析比应有的更复杂。
2赞 Simon 3/12/2015 #9

对于默认场景,我也建议使用 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 的 char 数组。例如:

String s = "     a bb   ccc  dddd eeeee  ffffff    ggggggg ";
ArrayList<String> result = splitBySingleChar(s.toCharArray(), ' ');
2赞 John29 4/22/2016 #10

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