2009-03-27 20 views
135

Ho appena saputo della classe Scanner di Java e ora mi chiedo come si confronti/compete con StringTokenizer e String.Split. So che StringTokenizer e String.Split funzionano solo su Stringhe, quindi perché dovrei voler utilizzare lo Scanner per una stringa? Scanner è pensato per essere uno sportello unico per la divisione?Scanner vs. StringTokenizer vs. String.Split

risposta

219

Sono essenzialmente cavalli per i corsi.

  • Scanner è progettato per i casi in cui è necessario analizzare una stringa, estraendo dati di tipi diversi. È molto flessibile, ma probabilmente non ti fornisce l'API più semplice per ottenere semplicemente una serie di stringhe delimitate da una particolare espressione.
  • String.split() e Pattern.split() forniscono una sintassi semplice per eseguire quest'ultima, ma in sostanza è tutto ciò che fanno. Se si desidera analizzare le stringhe risultanti o modificare il delimitatore a metà a seconda di un particolare token, non ti aiuteranno in questo modo.
  • StringTokenizer è ancora più restrittivo di String.split() e anche un po 'più fugace da usare. È essenzialmente progettato per estrarre token delimitati da sottostringhe fisse. A causa di questa restrizione, è circa il doppio della velocità di String.split(). (Vedi il mio comparison of String.split() and StringTokenizer). È anche precedente all'API delle espressioni regolari, di cui String.split() è una parte.

Avrete notato dai miei tempi che String.split() può ancora tokenize migliaia di stringhe in pochi millisecondi su una macchina tipica. Inoltre, ha il vantaggio rispetto a StringTokenizer che ti dà l'output come array di stringhe, che di solito è ciò che desideri. L'uso di uno Enumeration, come previsto da StringTokenizer, è troppo "sintatticamente pignolo" la maggior parte delle volte. Da questo punto di vista, lo StringTokenizer è un po 'uno spreco di spazio al giorno d'oggi, e si può anche solo usare String.split().

+0

Sì, molto perspicace! – Dave

+7

Sarebbe anche interessante vedere i risultati di Scanner sugli stessi test eseguiti su String.Split e StringTokenizer. – Dave

+1

Mi ha dato una risposta ad un'altra domanda: "perché l'uso di StringTokenizer è scoraggiato, come indicato nelle note dell'API Java?". Da questo testo sembra che la risposta sarebbe "perché String.split() è abbastanza veloce". – Legs

5

Se si dispone di un oggetto String che si desidera tokenizzare, si consiglia di utilizzare il metodo split di String su StringTokenizer. Se stai analizzando i dati di testo da un'origine esterna al tuo programma, come da un file o dall'utente, è a questo punto che uno scanner è utile.

+1

Proprio così, nessuna giustificazione, nessuna ragione? –

9

StringTokenizer era sempre lì. È il più veloce di tutti, ma l'idioma simile all'enumerazione potrebbe non sembrare elegante come gli altri.

suddivisione è venuto a esistenza su JDK 1.4. Più lento del tokenizer ma più facile da usare, dal momento che è richiamabile dalla classe String.

Lo scanner è arrivato su JDK 1.5. È il più flessibile e colma una lacuna di vecchia data sull'API Java per supportare un equivalente della famosa famiglia di funzioni Cf scanf.

+0

Correzione rapida: lo scanner è stato introdotto in JDK 1.5. – Dave

+0

Hai ragione! ... correggilo meglio. –

54

Iniziamo eliminando StringTokenizer. Sta invecchiando e non supporta nemmeno le espressioni regolari. La documentazione riporta:

StringTokenizer è una classe precedente che viene mantenuta per ragioni di compatibilità anche se il suo utilizzo è sconsigliato in un nuovo codice. Si consiglia a tutti coloro che cercano questa funzionalità di utilizzare il metodo split di String o il pacchetto java.util.regex.

Quindi buttiamolo via subito. Questo lascia split() e Scanner.Qual è la differenza tra loro?

Per prima cosa, split() restituisce semplicemente un array, che lo rende facile da usare un ciclo foreach:

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

Scanner è costruito più come un torrente:

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

o

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

(ha piuttosto large API, quindi non inchiostro che è sempre limitato a cose così semplici.)

Questa interfaccia in stile stream può essere utile per analizzare semplici file di testo o input di console, quando non si ha (o non si può ottenere) tutto l'input prima di iniziare analizzare.

Personalmente, l'unica volta che posso ricordare di usare Scanner è per i progetti scolastici, quando dovevo ottenere l'input dell'utente dalla riga di comando. Rende questo tipo di operazione facile. Ma se ho un String che voglio dividere, è quasi un gioco da ragazzi andare con split().

+14

StringTokenizer è 2x più veloce di String.split(). Se non hai BISOGNO di usare espressioni regolari, NON FARE! –

+0

Ho appena usato 'Scanner' per rilevare i nuovi caratteri di riga in un dato' String'. Poiché i caratteri della nuova riga possono variare da piattaforma a piattaforma (guarda javadoc!) Di Pattern e ** e ** NON è garantito che la stringa di input sia conforme a 'System.lineSeparator()', trovo 'Scanner' più adatto come sa già quali caratteri della nuova riga cercare quando chiamano 'nextLine()'. Per 'String.split' dovrò inserire il modello regex corretto per rilevare i separatori di riga, che non trovo memorizzati in alcuna posizione standard (il meglio che posso fare è copiarlo dalla sorgente 'Scanner' class') . – ADTC

3

String.split sembra molto più lento di StringTokenizer. L'unico vantaggio con split è che ottieni una serie di token. Inoltre puoi usare qualsiasi espressione regolare in split. org.apache.commons.lang.StringUtils ha un metodo split che funziona molto più velocemente di qualsiasi altro due. StringTokenizer o String.split. Ma l'utilizzo della CPU per tutti e tre è quasi lo stesso. Quindi, abbiamo anche bisogno di un metodo che richieda meno CPU, che non riesco ancora a trovare.

+3

Questa risposta è leggermente priva di senso. Dici che stai cercando qualcosa che sia più veloce ma "meno intensivo della CPU". Qualsiasi programma viene eseguito dalla CPU. Se un programma non utilizza la CPU al 100%, allora deve essere in attesa di qualcos'altro, come I/O. Questo non dovrebbe mai essere un problema quando si parla di tokenizzazione delle stringhe, a meno che non si stia facendo l'accesso diretto al disco (cosa che in particolare non stiamo facendo qui). – Jolta

4

Recentemente ho fatto alcuni esperimenti sulle cattive prestazioni di String.split() in situazioni altamente sensibili alle prestazioni. Potresti trovarlo utile

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

Il succo è che String.split() compila un modello di espressione regolare di volta in volta e può quindi rallentare il programma, rispetto a se si utilizza un oggetto modello precompilato e utilizzarlo direttamente a operare su un Stringa.

+4

In realtà String.split() non compila sempre il modello. Guardate il sorgente se 1.7 java, vedrete che c'è un segno di spunta se il pattern è un singolo carattere e non uno di escape, esso dividerà la stringa senza regexp, quindi dovrebbe essere abbastanza veloce. –

6

Lo split è lento, ma non lento come lo scanner. StringTokenizer è più veloce di split. Tuttavia, ho scoperto che avrei potuto ottenere il doppio della velocità, commerciando una certa flessibilità, per ottenere una velocità-boost, che ho fatto a JFastParser https://github.com/hughperkins/jfastparser

test su una stringa contenente un milione di doppie:

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

Qualche Javadoc sarebbe stato carino, e se volessimo analizzare qualcosa di diverso dai dati numerici? – NickJ

+0

Beh, è ​​progettato per la velocità, non per la bellezza. È piuttosto semplice, solo poche righe, quindi potresti aggiungere qualche altra opzione per l'analisi del testo, se lo desideri. –

-6

String.split() funziona molto bene ma ha i suoi limiti, come se volessi dividere una stringa come mostrato di seguito in base al simbolo a tubo singolo o doppio (|), non funziona. In questa situazione puoi usare StringTokenizer.

ABC | IJK

+12

In realtà, puoi dividere il tuo esempio con solo "ABC | IJK" .split ("\\ |"); – Tomo

+0

"ABC || DEF ||" .split ("\\ |") in realtà non funziona perché ignorerà i due valori vuoti finali, il che rende l'analisi più complicata di quanto dovrebbe essere. – Armand

1

Per gli scenari predefiniti vorrei suggerire Pattern.split() come pure, ma se hai bisogno il massimo delle prestazioni (in particolare su Android tutte le soluzioni che ho provato sono piuttosto lento) e avete solo bisogno di raggruppati per un singolo carattere, io uso il mio metodo:

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

Usa "abc" .toCharArray() per ottenere l'array char per una stringa.Per esempio:

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

Una differenza importante è che sia String.split() e scanner in grado di produrre stringhe vuote ma StringTokenizer non lo fa.

Ad esempio:

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

uscita:

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

Questo perché il delimitatore per String.split() e Scanner.useDelimiter() non è solo una stringa, ma un'espressione regolare . Possiamo sostituire il delimitatore "" con "+" nell'esempio sopra per far sì che si comportino come StringTokenizer.

Problemi correlati