Scanner vs. StringTokenizer vs. String.Split


155

Ho appena imparato a conoscere la classe Scanner di Java e ora mi chiedo come si confronta / compete con StringTokenizer e String.Split. So che StringTokenizer e String.Split funzionano solo su stringhe, quindi perché dovrei usare lo scanner per una stringa? Scanner è pensato per essere lo sportello unico per la divisione?

Risposte:


240

Sono essenzialmente cavalli per i corsi.

  • Scannerè progettato per i casi in cui è necessario analizzare una stringa, estraendo dati di diversi tipi. È molto flessibile, ma probabilmente non ti dà l'API più semplice per ottenere semplicemente una matrice di stringhe delimitata da una particolare espressione.
  • String.split()e Pattern.split()ti dà una sintassi facile per fare quest'ultimo, ma è essenzialmente tutto ciò che fanno. Se desideri analizzare le stringhe risultanti o modificare il delimitatore a metà in base a un determinato token, non ti aiuteranno.
  • StringTokenizerè ancora più restrittivo di String.split(), e anche un po 'più complicato da usare. È progettato essenzialmente per estrarre token delimitati da sottostringhe fisse. A causa di questa limitazione, è circa il doppio della velocità String.split(). (Vedi il mio confronto di String.split()eStringTokenizer .) Precede anche l'API delle espressioni regolari, di cui String.split()fa parte.

Noterai dai miei tempi che String.split()possono ancora tokenizzare migliaia di stringhe in pochi millisecondi su una macchina tipica. Inoltre, ha il vantaggio di StringTokenizerdarti l'output come array di stringhe, che di solito è quello che vuoi. L'uso di un Enumeration, come fornito da StringTokenizer, è troppo "sintatticamente pignolo" per la maggior parte del tempo. Da questo punto di vista, StringTokenizeroggigiorno è un po 'uno spreco di spazio, e puoi anche semplicemente usare String.split().


8
Sarebbe anche interessante vedere i risultati dello scanner sugli stessi test eseguiti su String.Split e StringTokenizer.
Dave,

2
Mi ha dato una risposta a 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

1
Quindi StringTokenizer è praticamente deprecato adesso?
Steve the Maker,

cosa usare al posto di esso? Scanner?
Adrian,

4
Mi rendo conto che è una risposta a una vecchia domanda, ma se ho bisogno di dividere un enorme flusso di testo in token al volo, non è StringTokenizerancora la mia scommessa migliore perché String.split()rimarrà semplicemente a corto di memoria?
Sergei Tachenov,

57

Cominciamo eliminando StringTokenizer. Sta invecchiando e non supporta nemmeno le espressioni regolari. La sua documentazione afferma:

StringTokenizerè una classe legacy che viene mantenuta per motivi di compatibilità sebbene il suo utilizzo sia sconsigliato nel nuovo codice. Si consiglia a chiunque cerchi questa funzionalità di utilizzare invece il splitmetodo Stringo il java.util.regexpacchetto.

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

Per prima cosa, split()restituisce semplicemente un array, il che semplifica l'uso di un ciclo foreach:

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

Scanner è costruito più come uno stream:

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

o

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

(Ha un'API piuttosto grande , quindi non pensare che sia sempre limitato a cose così semplici.)

Questa interfaccia in stile stream può essere utile per analizzare semplici file di testo o input della console, quando non hai (o non riesci a ottenere) tutti gli input prima di iniziare l'analisi.

Personalmente, l'unica volta che ricordo di aver usato Scannerè per i progetti scolastici, quando ho dovuto ottenere l'input dell'utente dalla riga di comando. Rende facile questo tipo di operazione. Ma se ne ho uno Stringche voglio dividere, è quasi un gioco da ragazzi split().


20
StringTokenizer è 2 volte più veloce di String.split (). Se NON AVETE BISOGNO di usare espressioni regolari, NON FATE!
Alex Worden,

Ho appena usato Scannerper rilevare i nuovi caratteri di linea in un dato String. Dato che i nuovi caratteri di linea possono variare da piattaforma a piattaforma (guarda Patternjavadoc!) E NON è garantito che la stringa di input sia conforme System.lineSeparator(), trovo Scannerpiù adatta in quanto sa già quali nuovi caratteri di linea cercare quando chiamare nextLine(). Per String.splitdovrò nutrire nel modello regex corretta per rilevare separatori di linea, che non trovo memorizzati in qualsiasi posizione standard (il meglio che posso fare è copiare dalla Scannersorgente della classe).
ADTC

9

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

la divisione venne alla luce su JDK 1.4. Più lento del tokenizer ma più facile da usare, poiché è richiamabile dalla classe String.

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


6

Se hai un oggetto String che vuoi tokenizzare, preferisci usare il metodo split di String su un StringTokenizer. Se stai analizzando i dati di testo da una fonte esterna al tuo programma, come da un file o dall'utente, è qui che lo scanner è utile.


5
Proprio così, nessuna giustificazione, nessuna ragione?
Jan.supol,

6

La divisione è lenta, ma non lenta come lo scanner. StringTokenizer è più veloce della divisione. Tuttavia, ho scoperto che avrei potuto raddoppiare la velocità, scambiando una certa flessibilità, per ottenere un aumento di velocità, cosa che ho fatto su 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

Alcuni Javadoc sarebbero stati carini, e se volessi analizzare qualcosa di diverso dai dati numerici?
NickJ,

Bene, è progettato per la velocità, non per la bellezza. È abbastanza semplice, solo poche righe, quindi puoi aggiungere qualche altra opzione per l'analisi del testo, se lo desideri.
Hugh Perkins,

4

String.split sembra essere molto più lento di StringTokenizer. L'unico vantaggio con split è che ottieni una matrice di token. Inoltre puoi usare qualsiasi espressione regolare divisa. org.apache.commons.lang.StringUtils ha un metodo split che funziona molto più velocemente di uno qualsiasi dei due vale a dire. 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 per la CPU". Qualsiasi programma viene eseguito dalla CPU. Se un programma non utilizza la CPU al 100%, deve attendere qualcos'altro, come l'I / O. Questo non dovrebbe mai essere un problema quando si discute di tokenizzazione delle stringhe, a meno che non si stia facendo un accesso diretto al disco (cosa che non stiamo facendo qui).
Jolta

4

Di recente ho fatto alcuni esperimenti sulle cattive prestazioni di String.split () in situazioni altamente sensibili alle prestazioni. Potrebbe essere utile.

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

Il punto è che String.split () compila ogni volta un modello di espressione regolare e può quindi rallentare il programma, rispetto a se si utilizza un oggetto modello precompilato e lo si utilizza direttamente per operare su una stringa.


4
In realtà String.split () non sempre compila il modello. Guarda la fonte se 1.7 java, vedrai che c'è un controllo se il modello è un singolo carattere e non un carattere di escape, dividerà la stringa senza regexp, quindi dovrebbe essere abbastanza veloce.
Krzysztof Krasoń

1

Per gli scenari predefiniti suggerirei anche Pattern.split () ma se hai bisogno delle massime prestazioni (specialmente su Android tutte le soluzioni che ho testato sono piuttosto lente) e devi solo dividere per un singolo carattere, ora 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;
}

Utilizzare "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 () che Scanner possono produrre stringhe vuote ma StringTokenizer non lo fa mai.

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

Produzione:

//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 farli comportare come StringTokenizer.


-5

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

ABC | IJK


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

"ABC || DEF ||" .split ("\\ |") non funziona davvero perché ignorerà i due valori vuoti finali, il che rende l'analisi più complicata di quanto dovrebbe essere.
Armand,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.