Scanner vs. StringTokenizer vs. String.Split

155

Acabo de enterarme de la clase Scanner de Java y ahora me pregunto cómo se compara / compite con StringTokenizer y String.Split. Sé que StringTokenizer y String.Split solo funcionan en Strings, entonces, ¿por qué querría usar el escáner para una cadena? ¿Scanner está destinado a ser una ventanilla única para la división?

Dave
fuente

Respuestas:

240

Son esencialmente caballos para cursos.

  • Scannerestá diseñado para casos en los que necesita analizar una cadena, extrayendo datos de diferentes tipos. Es muy flexible, pero podría decirse que no le brinda la API más simple para simplemente obtener una matriz de cadenas delimitadas por una expresión particular.
  • String.split()y Pattern.split()darle una sintaxis fácil para hacer esto último, pero eso es esencialmente todo lo que hacen. Si desea analizar las cadenas resultantes o cambiar el delimitador a mitad de camino según un token en particular, no lo ayudarán con eso.
  • StringTokenizeres aún más restrictivo que String.split(), y también un poco más complicado de usar. Está esencialmente diseñado para extraer tokens delimitados por subcadenas fijas. Debido a esta restricción, es aproximadamente el doble de rápido que String.split(). (Consulte mi comparación de String.split()yStringTokenizer .) También es anterior a la API de expresiones regulares, de la cual String.split()forma parte.

Notarás por mis tiempos que String.split()aún pueden tokenizar miles de cadenas en unos pocos milisegundos en una máquina típica. Además, tiene la ventaja de StringTokenizerque le proporciona la salida como una matriz de cadenas, que generalmente es lo que desea. Usar un Enumeration, según lo provisto StringTokenizer, es demasiado "sintácticamente exigente" la mayor parte del tiempo. Desde este punto de vista, StringTokenizeres un poco una pérdida de espacio hoy en día, y también puede usarlo String.split().

Neil Coffey
fuente
8
También sería interesante ver los resultados de Scanner en las mismas pruebas que ejecutó en String.Split y StringTokenizer.
Dave
2
Me dio una respuesta a otra pregunta: "¿por qué se desaconseja el uso de StringTokenizer, como se indica en las notas de la API Java?". De este texto parece que la respuesta sería "porque String.split () es lo suficientemente rápido".
Piernas
1
Entonces, ¿StringTokenizer está bastante obsoleto ahora?
Steve the Maker
¿Qué usar en lugar de él? ¿Escáner?
Adrian
44
Me doy cuenta de que es una respuesta a una vieja pregunta, pero si necesito dividir una gran secuencia de texto en tokens sobre la marcha, ¿no es StringTokenizermi mejor opción porque String.split()simplemente se quedará sin memoria?
Sergei Tachenov
57

Comencemos por eliminar StringTokenizer. Se está haciendo viejo y ni siquiera admite expresiones regulares. Su documentación establece:

StringTokenizeres una clase heredada que se retiene por razones de compatibilidad, aunque se desaconseja su uso en nuevos códigos. Se recomienda que cualquiera que busque esta funcionalidad use el splitmétodo Stringo el java.util.regexpaquete en su lugar.

Así que vamos a tirarlo de inmediato. Eso se va split()y Scanner. ¿Cual es la diferencia entre ellos?

Por un lado, split()simplemente devuelve una matriz, lo que facilita el uso de un bucle foreach:

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

Scanner se construye más como una secuencia:

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

o

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

(Tiene una API bastante grande , así que no pienses que siempre está restringido a cosas tan simples).

Esta interfaz de estilo de transmisión puede ser útil para analizar archivos de texto simples o entradas de consola, cuando no tiene (o no puede obtener) toda la entrada antes de comenzar a analizar.

Personalmente, el único momento que recuerdo haber usado Scanneres para proyectos escolares, cuando tuve que obtener la entrada del usuario desde la línea de comandos. Hace que este tipo de operación sea fácil. Pero si tengo una con la Stringque quiero separarme, es casi una obviedad split().

Michael Myers
fuente
20
StringTokenizer es 2 veces más rápido que String.split (). Si NO NECESITA usar expresiones regulares, ¡NO LO HAGA!
Alex Worden
Solía Scannerdetectar nuevos caracteres de línea en un determinado String. Dado que los nuevos caracteres de línea pueden variar de una plataforma a otra (¡mire Patternel javadoc!) Y NO se garantiza que la cadena de entrada se ajuste System.lineSeparator(), creo que es Scannermás adecuado ya que ya sabe qué nuevos caracteres de línea debe buscar al llamar nextLine(). Para String.splitque tendrá que alimentar en el patrón de expresión correcta para detectar separadores de línea, que no encuentro almacenados en cualquier posición estándar (lo mejor que puedo hacer es copiar desde la Scannerfuente de la clase).
ADTC
9

StringTokenizer siempre estuvo ahí. Es el más rápido de todos, pero el lenguaje de enumeración puede no parecer tan elegante como los demás.

split surgió en JDK 1.4. Más lento que el tokenizador pero más fácil de usar, ya que se puede llamar desde la clase String.

El escáner llegó a estar en JDK 1.5. Es el más flexible y llena un vacío de larga data en la API de Java para admitir un equivalente de la famosa familia de funciones Cs scanf.

H Marcelo Morales
fuente
6

Si tiene un objeto String que desea tokenizar, favorezca el uso del método de división de String sobre un StringTokenizer. Si está analizando datos de texto desde una fuente externa a su programa, como desde un archivo o desde el usuario, ahí es muy útil un escáner.

Bill el lagarto
fuente
55
Solo así, ¿sin justificación, sin razón?
ene.supol
6

La división es lenta, pero no tan lenta como el escáner. StringTokenizer es más rápido que dividir. Sin embargo, descubrí que podía obtener el doble de velocidad, intercambiando cierta flexibilidad, para obtener un aumento de velocidad, lo que hice en JFastParser https://github.com/hughperkins/jfastparser

Prueba en una cadena que contiene un millón de dobles:

Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms
Hugh Perkins
fuente
Algún Javadoc hubiera sido bueno, y ¿qué pasa si desea analizar algo más que datos numéricos?
NickJ
Bueno, está diseñado para la velocidad, no para la belleza. Es bastante simple, solo unas pocas líneas, por lo que puede agregar algunas opciones más para el análisis de texto si lo desea.
Hugh Perkins
4

String.split parece ser mucho más lento que StringTokenizer. La única ventaja de dividir es que obtienes una variedad de tokens. También puede usar cualquier expresión regular en división. org.apache.commons.lang.StringUtils tiene un método dividido que funciona mucho más rápido que cualquiera de los dos a saber. StringTokenizer o String.split. Pero la utilización de la CPU para los tres es casi la misma. Por lo tanto, también necesitamos un método que requiera menos CPU, que todavía no puedo encontrar.

Manish
fuente
3
Esta respuesta es un poco absurda. Dices que estás buscando algo que sea más rápido pero "menos intensivo de CPU". Cualquier programa es ejecutado por la CPU. Si un programa no utiliza su CPU al 100%, debe estar esperando algo más, como E / S. Eso nunca debería ser un problema cuando se discute la tokenización de cadenas, a menos que esté haciendo acceso directo al disco (que notablemente no estamos haciendo aquí).
Jolta
4

Recientemente realicé algunos experimentos sobre el mal rendimiento de String.split () en situaciones muy sensibles al rendimiento. Puede encontrar esto útil.

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

La esencia es que String.split () compila un patrón de Expresión Regular cada vez y, por lo tanto, puede ralentizar su programa, en comparación con si usa un objeto Patrón precompilado y lo usa directamente para operar en una Cadena.

Pdeva
fuente
44
En realidad, String.split () no siempre compila el patrón. Mire la fuente si 1.7 java, verá que hay una comprobación si el patrón es un solo carácter y no uno escapado, dividirá la cadena sin regexp, por lo que debería ser bastante rápido.
Krzysztof Krasoń
1

Para los escenarios predeterminados, sugeriría Pattern.split () también, pero si necesita un rendimiento máximo (especialmente en Android, todas las soluciones que probé son bastante lentas) y solo necesita dividir por un solo carácter, ahora uso mi propio 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 obtener la matriz de caracteres para una Cadena. Por ejemplo:

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

Una diferencia importante es que String.split () y Scanner pueden producir cadenas vacías, pero StringTokenizer nunca lo hace.

Por ejemplo:

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

Salida:

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

Esto se debe a que el delimitador para String.split () y Scanner.useDelimiter () no es solo una cadena, sino una expresión regular. Podemos reemplazar el delimitador "" con "+" en el ejemplo anterior para que se comporten como StringTokenizer.

John29
fuente
-5

String.split () funciona muy bien pero tiene sus propios límites, como si quisiera dividir una cadena como se muestra a continuación en función del símbolo de tubería simple o doble (|), no funciona. En esta situación, puede usar StringTokenizer.

ABC | IJK

Mujahid shaik
fuente
12
En realidad, puede dividir su ejemplo con solo "ABC | IJK" .split ("\\ |");
Tomo
"ABC || DEF ||" .split ("\\ |") realmente no funciona porque ignorará los dos valores vacíos finales, lo que hace que el análisis sea más complicado de lo que debería ser.
Armand