Scanner vs. StringTokenizer vs. String.Split

155

Acabei de aprender sobre a classe Scanner do Java e agora estou me perguntando como ele se compara / compete com o StringTokenizer e o String.Split. Eu sei que o StringTokenizer e o String.Split funcionam apenas em Strings, então por que eu gostaria de usar o Scanner para uma String? O Scanner destina-se apenas a ser um balcão único para a divisão?

Dave
fonte

Respostas:

240

Eles são essencialmente cavalos para percursos.

  • Scannerfoi desenvolvido para casos em que você precisa analisar uma string, obtendo dados de diferentes tipos. É muito flexível, mas sem dúvida não fornece a API mais simples para simplesmente obter uma matriz de seqüências delimitadas por uma expressão específica.
  • String.split()e Pattern.split()fornecer uma sintaxe fácil para você fazer o último, mas isso é basicamente tudo o que eles fazem. Se você deseja analisar as seqüências de caracteres resultantes ou alterar o delimitador no meio, dependendo de um token específico, eles não o ajudarão nisso.
  • StringTokenizeré ainda mais restritivo do que String.split()e também um pouco mais difícil de usar. Ele foi projetado essencialmente para extrair tokens delimitados por substrings fixos. Por causa dessa restrição, é duas vezes mais rápido que String.split(). (Veja minha comparação de String.split()eStringTokenizer .) Ele também antecede a API de expressões regulares, da qual String.split()faz parte.

Você notará pelos meus tempos que String.split()ainda podem tokenizar milhares de strings em alguns milissegundos em uma máquina típica. Além disso, tem a vantagem de StringTokenizerfornecer a saída como uma matriz de strings, que geralmente é o que você deseja. Usar um Enumeration, como fornecido por StringTokenizer, é muito "sintaticamente exigente" na maioria das vezes. Deste ponto de vista, StringTokenizeré um pouco de desperdício de espaço hoje em dia, e você também pode usar String.split().

Neil Coffey
fonte
8
Também seria interessante ver os resultados do scanner nos mesmos testes que você executou no String.Split e StringTokenizer.
31409 Dave
2
Deu-me uma resposta para outra pergunta: "por que o uso do StringTokenizer é desencorajado, conforme declarado nas notas da API Java?". A partir deste texto, parece que a resposta seria "porque String.split () é rápido o suficiente".
pernas
1
Então, o StringTokenizer está praticamente obsoleto agora?
Steve o Criador
o que usar em vez dele? Scanner?
Adrian
4
Sei que é uma resposta para uma pergunta antiga, mas se eu precisar dividir um fluxo enorme de texto em tokens rapidamente, essa StringTokenizerainda não é minha melhor aposta, porque String.split()simplesmente ficará sem memória?
Sergei Tachenov 26/01
57

Vamos começar eliminando StringTokenizer. Está ficando velho e nem suporta expressões regulares. Sua documentação declara:

StringTokenizeré uma classe herdada que é mantida por motivos de compatibilidade, embora seu uso seja desencorajado em novo código. Recomenda-se que quem procura essa funcionalidade use o splitmétodo Stringou o java.util.regexpacote.

Então, vamos jogar fora imediatamente. Isso sai split()e Scanner. Qual a diferença entre eles?

Por um lado, split()simplesmente retorna uma matriz, o que facilita o uso de um loop foreach:

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

Scanner é construído mais como um fluxo:

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

ou

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

(Ele possui uma API bastante grande , não pense que ela está sempre restrita a coisas tão simples.)

Essa interface no estilo de fluxo pode ser útil para analisar arquivos de texto simples ou entrada do console, quando você não possui (ou não pode obter) toda a entrada antes de começar a analisar.

Pessoalmente, a única vez em que me lembro de usar Scanneré em projetos escolares, quando tive que obter informações do usuário na linha de comando. Isso facilita esse tipo de operação. Mas se eu tiver um Stringque eu quero dividir, é quase um acéfalo para acompanhar split().

Michael Myers
fonte
20
StringTokenizer é 2x mais rápido que String.split (). Se você NÃO PRECISA usar expressões regulares, NÃO!
Alex Worden
Eu apenas costumava Scannerdetectar novos caracteres de linha em um dado String. Como os novos caracteres de linha podem variar de plataforma para plataforma (veja Patterno javadoc!) E a sequência de entrada NÃO é garantida System.lineSeparator(), eu acho Scannermais adequado, pois ele já sabe quais novos caracteres de linha procurar ao chamar nextLine(). Pois String.splitterei que alimentar o padrão de regex correto para detectar os separadores de linha, que não encontro armazenados em nenhum local padrão (o melhor que posso fazer é copiá-lo da Scannerfonte da classe).
ADTC 16/08
9

O StringTokenizer estava sempre lá. É o mais rápido de todos, mas o idioma do tipo enumeração pode não parecer tão elegante quanto os outros.

a divisão surgiu no JDK 1.4. Mais lento que o tokenizer, mas mais fácil de usar, pois é possível chamar da classe String.

O scanner chegou ao JDK 1.5. É o mais flexível e preenche uma lacuna de longa data na API Java para suportar um equivalente da famosa família de funções Cs scanf.

H Marcelo Morales
fonte
6

Se você tiver um objeto String que deseja tokenizar, use o método split da String em vez de um StringTokenizer. Se você estiver analisando dados de texto de uma fonte externa ao seu programa, como de um arquivo ou do usuário, é aí que um Scanner é útil.

Bill the Lizard
fonte
5
Só assim, sem justificativa, sem motivo?
jan.supol
6

A divisão é lenta, mas não tão lenta quanto o Scanner. O StringTokenizer é mais rápido que o split. No entanto, descobri que poderia obter o dobro da velocidade, trocando alguma flexibilidade, para obter um aumento de velocidade, o que fiz no JFastParser https://github.com/hughperkins/jfastparser

Teste em uma string contendo um milhão de vezes:

Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms
Hugh Perkins
fonte
Algum Javadoc teria sido bom, e se você quiser analisar algo diferente de dados numéricos?
NickJ
Bem, ele foi projetado para velocidade, não beleza. É bastante simples, apenas algumas linhas, para que você possa adicionar mais algumas opções para análise de texto, se desejar.
Hugh Perkins
4

String.split parece ser muito mais lento que StringTokenizer. A única vantagem da divisão é que você obtém uma matriz de tokens. Além disso, você pode usar qualquer expressão regular em divisão. org.apache.commons.lang.StringUtils possui um método de divisão que funciona muito mais rápido do que qualquer um dos dois viz. StringTokenizer ou String.split. Mas a utilização da CPU para todos os três é quase a mesma. Por isso, também precisamos de um método que consome menos CPU, que ainda não consigo encontrar.

Manish
fonte
3
Esta resposta é um pouco absurda. Você diz que está procurando algo mais rápido, mas "menos intensivo em CPU". Qualquer programa é executado pela CPU. Se um programa não utiliza sua CPU 100%, ele deve estar aguardando outra coisa, como E / S. Isso nunca deve ser um problema ao discutir a tokenização de strings, a menos que você esteja acessando diretamente o disco (o que notavelmente não estamos fazendo aqui).
Jolta
4

Recentemente, fiz alguns experimentos sobre o mau desempenho de String.split () em situações altamente sensíveis ao desempenho. Você pode achar isso útil.

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

A essência é que String.split () compila um padrão de Expressão Regular a cada vez e, portanto, pode tornar seu programa mais lento, comparado a se você usar um objeto Pattern pré-compilado e usá-lo diretamente para operar em uma String.

pdeva
fonte
4
Na verdade, String.split () nem sempre compila o padrão. Olhe a fonte se 1.7 java, você verá que há uma verificação se o padrão é um caractere único e não escapou, ele dividirá a string sem regexp, portanto deve ser bastante rápido.
Krzysztof Krasoń 7/11
1

Para os cenários padrão, eu sugeriria Pattern.split () também, mas se você precisar de desempenho máximo (especialmente no Android, todas as soluções que testei são bastante lentas) e você só precisará dividir por um único caractere, agora uso meu próprio método:

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;
}

Use "abc" .toCharArray () para obter a matriz char para uma String. Por exemplo:

String s = "     a bb   ccc  dddd eeeee  ffffff    ggggggg ";
ArrayList<String> result = splitBySingleChar(s.toCharArray(), ' ');
Simon
fonte
1

Uma diferença importante é que o String.split () e o Scanner podem produzir cadeias vazias, mas o StringTokenizer nunca o faz.

Por exemplo:

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

Resultado:

//StringTokenizer
#0: ab
#1: cd
#2: ef
//String.split()
#0: ab
#1: cd
#2: 
#3: ef
//Scanner
#0: ab
#1: cd
#2: 
#3: ef

Isso ocorre porque o delimitador para String.split () e Scanner.useDelimiter () não é apenas uma sequência, mas uma expressão regular. Podemos substituir o delimitador "" por "+" no exemplo acima para fazê-lo se comportar como StringTokenizer.

John29
fonte
-5

String.split () funciona muito bem, mas possui seus próprios limites, como se você quisesse dividir uma string como mostrado abaixo com base no símbolo de barra simples ou dupla (|), ela não funciona. Nessa situação, você pode usar o StringTokenizer.

ABC | IJK

Mujahid shaik
fonte
12
Na verdade, você pode dividir seu exemplo com apenas "ABC | IJK" .split ("\\ |");
Tomo
"ABC || DEF ||" .split ("\\ |") não funciona realmente porque ignora os dois valores vazios à direita, o que torna a análise mais complicada do que deveria.
Armand