Scanner vs. StringTokenizer vs. String.Split

Ho appena saputo della class 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 voler utilizzare lo Scanner per una stringa? Scanner è pensato per essere uno sportello unico per la divisione?

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() ti danno una syntax facile per fare il secondo, ma questo è essenzialmente 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 confronto su String.split() e StringTokenizer .) Precede anche l’API delle espressioni regolari, di cui String.split() è una parte.

Noterai dai miei tempi che String.split() può ancora tokenizzare 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 Enumeration , come fornito da StringTokenizer , è troppo “sintatticamente pignolo” la maggior parte delle volte. Da questo punto di vista, StringTokenizer è un po ‘uno spreco di spazio al giorno d’oggi, e si può anche solo usare String.split() .

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

StringTokenizer è una class legacy che viene mantenuta per ragioni di compatibilità anche se il suo uso è scoraggiato nel nuovo codice. Si raccomanda che chiunque cerchi questa funzionalità utilizzi il metodo split di String o il pacchetto java.util.regex .

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

Per prima cosa, split() restituisce semplicemente un array, il che rende facile l’uso di un ciclo foreach:

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

Scanner è costruito più come un 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 da console, quando non si ha (o non si può ottenere) tutto l’input prima di iniziare l’analisi.

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

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

la divisione è entrata in esistenza su JDK 1.4. Più lento del tokenizer ma più facile da usare, dal momento che è richiamabile dalla class 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.

La divisione è lenta, ma non così lenta come lo scanner. StringTokenizer è più veloce di split. Tuttavia, ho scoperto che potevo ottenere il doppio della velocità, scambiando un po ‘di flessibilità, per ottenere un aumento della velocità, cosa che ho fatto a JFastParser https://github.com/hughperkins/jfastparser

Test su una stringa contenente un milione di doppi:

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

Se si dispone di un object String che si desidera tokenize, 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.

String.split sembra essere 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.

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 gist è che String.split () compila ogni volta un pattern di espressione regolare e può quindi rallentare il tuo programma, se usato un object Pattern precompilato e lo usi direttamente per operare su una stringa.

Per gli scenari predefiniti suggerirei Pattern.split (), ma se hai bisogno di prestazioni massime (specialmente su Android tutte le soluzioni che ho provato sono piuttosto lente) e devi solo dividere un singolo carattere, ora uso il mio metodo:

 public static ArrayList splitBySingleChar(final char[] s, final char splitChar) { final ArrayList result = new ArrayList(); 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 il char array per una stringa. Per esempio:

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

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 di 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.

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