Scanner vs. StringTokenizer vs. String.Split

Acabo de enterarme de la clase de escáner 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 Scanner for a String? ¿Scanner solo pretende ser una ventanilla única para spliting?

Son esencialmente caballos para los cursos.

  • Scanner está 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 da la API más simple para simplemente obtener una matriz de cadenas delimitadas por una expresión particular.
  • String.split() y Pattern.split() dan una syntax 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 la mitad dependiendo de un token en particular, no lo ayudarán con eso.
  • StringTokenizer es aún más restrictivo que String.split() , y también un poco más difícil de usar. Está diseñado esencialmente 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() y StringTokenizer ). También es anterior a la API de expresiones regulares, de la cual String.split() es una parte.

Notará a partir de mis tiempos que String.split() aún puede tokenizar miles de cadenas en unos pocos milisegundos en una máquina típica. Además, tiene la ventaja sobre StringTokenizer que le da el resultado como una matriz de cadenas, que generalmente es lo que desea. El uso de una Enumeration , como lo proporciona StringTokenizer , es demasiado “sintácticamente quisquilloso” la mayor parte del tiempo. Desde este punto de vista, StringTokenizer es una pérdida de espacio hoy en día, y también puede usar String.split() .

Comencemos eliminando StringTokenizer . Se está haciendo viejo y ni siquiera admite expresiones regulares. Su documentación dice:

StringTokenizer es una clase heredada que se conserva por razones de compatibilidad, aunque se desaconseja su uso en el nuevo código. Se recomienda que cualquiera que busque esta funcionalidad use el método de split de String o el paquete java.util.regex lugar.

Así que vamos a tirarlo de inmediato. Eso deja 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 está construido 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 , por lo que no creo que siempre esté restringido a cosas tan simples).

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

Personalmente, la única vez que puedo recordar usar Scanner es para proyectos escolares, cuando tuve que obtener la entrada del usuario desde la línea de comando. Hace que ese tipo de operación sea fácil. Pero si tengo un String que quiero dividir, es casi una obviedad ir con split() .

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

split llegó a existir en JDK 1.4. Más lento que tokenizer pero más fácil de usar, ya que se puede llamar desde la clase String.

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

Split es lento, pero no tan lento como Scanner. StringTokenizer es más rápido que split. 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 

Si tiene un objeto String que desea convertir en token, use el método de división de String sobre un StringTokenizer. Si está analizando datos de texto desde una fuente fuera de su progtwig, como desde un archivo, o desde el usuario, ahí es donde un escáner es útil.

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

Recientemente hice algunos experimentos sobre el mal rendimiento de String.split () en situaciones de alto rendimiento. Puede encontrar esto útil.

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

Lo esencial es que String.split () comstack un patrón de Expresión regular cada vez y, por lo tanto, puede ralentizar el progtwig, en comparación con si usa un objeto Patrón precomstackdo y lo usa directamente para operar en una Cadena.

Para los escenarios predeterminados, sugeriría Pattern.split () también, pero si necesita el máximo rendimiento (especialmente en Android, todas las soluciones que probé son bastante lentas) y solo necesita dividirlo por un solo carácter, ahora uso mi propio método:

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

Use “abc” .toCharArray () para obtener la matriz de caracteres para una Cadena. Por ejemplo:

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

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 de String.split () y Scanner.useDelimiter () no es solo una cadena, sino una expresión regular. Podemos reemplazar el delimitador "" por "+" en el ejemplo anterior para hacer que se comporten como StringTokenizer.

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

ABC | IJK