tokens - Rendimiento de la clase StringTokenizer frente al método String.split en Java
stringtokenizer java 8 (10)
En mi software, necesito dividir la cadena en palabras. Actualmente tengo más de 19,000,000 de documentos con más de 30 palabras cada uno.
¿Cuál de las dos formas siguientes es la mejor manera de hacerlo (en términos de rendimiento)?
StringTokenizer sTokenize = new StringTokenizer(s," ");
while (sTokenize.hasMoreTokens()) {
o
String[] splitS = s.split(" ");
for(int i =0; i < splitS.length; i++)
¿Qué tienen que hacer los 19,000,000 de documentos allí? ¿Tiene que dividir palabras en todos los documentos de forma regular? ¿O es un problema de una sola vez?
Si visualiza / solicita un documento a la vez, con solo 30 palabras, este es un problema tan pequeño que cualquier método funcionaría.
Si tiene que procesar todos los documentos a la vez, con solo 30 palabras, este es un problema tan pequeño que es más probable que esté obligado a IO de todos modos.
El rendimiento de StringTokeniser es mucho mejor que la división. Verifica el código a continuación,
Pero según los documentos de Java, se desaconseja su uso. Mira Here
Esta podría ser una evaluación comparativa razonable con 1.6.0
http://www.javamex.com/tutorials/regular_expressions/splitting_tokenisation_performance.shtml#.V6-CZvnhCM8
Independientemente de su estado heredado, esperaría que StringTokenizer
fuera significativamente más rápido que String.split()
para esta tarea, porque no usa expresiones regulares: simplemente escanea la entrada directamente, de forma muy parecida a como lo haría usted mismo a través de indexOf()
. De hecho, String.split()
tiene que compilar la expresión regular cada vez que la llamas, por lo que ni siquiera es tan eficiente como usar directamente una expresión regular.
La especificación API de Java recomienda el uso de split
. Ver la documentación de StringTokenizer
.
Mientras se ejecutan pruebas de rendimiento micro (y en este caso, incluso nano), hay muchas cosas que afectan sus resultados. Optimizaciones JIT y recolección de basura, por nombrar solo algunas.
Para obtener resultados significativos de los micro benchmarks, consulte la biblioteca jmh . Tiene excelentes muestras agrupadas sobre cómo ejecutar buenos puntos de referencia.
Otra cosa importante, no documentada por lo que noté, es que pedir al StringTokenizer que devuelva los delimitadores junto con la cadena tokenizada (usando el constructor StringTokenizer(String str, String delim, boolean returnDelims)
) también reduce el tiempo de procesamiento. Entonces, si buscas rendimiento, te recomendaría usar algo como:
private static final String DELIM = "#";
public void splitIt(String input) {
StringTokenizer st = new StringTokenizer(input, DELIM, true);
while (st.hasMoreTokens()) {
String next = getNext(st);
System.out.println(next);
}
}
private String getNext(StringTokenizer st){
String value = st.nextToken();
if (DELIM.equals(value))
value = null;
else if (st.hasMoreTokens())
st.nextToken();
return value;
}
A pesar de los gastos generales introducidos por el método getNext (), que descarta los delimitadores para usted, sigue siendo un 50% más rápido de acuerdo con mis puntos de referencia.
Si sus datos ya están en una base de datos, necesita analizar la cadena de palabras, sugeriría utilizar indexOf repetidamente. Es mucho más rápido que cualquiera de las soluciones.
Sin embargo, obtener los datos de una base de datos aún es mucho más costoso.
StringBuilder sb = new StringBuilder();
for (int i = 100000; i < 100000 + 60; i++)
sb.append(i).append('' '');
String sample = sb.toString();
int runs = 100000;
for (int i = 0; i < 5; i++) {
{
long start = System.nanoTime();
for (int r = 0; r < runs; r++) {
StringTokenizer st = new StringTokenizer(sample);
List<String> list = new ArrayList<String>();
while (st.hasMoreTokens())
list.add(st.nextToken());
}
long time = System.nanoTime() - start;
System.out.printf("StringTokenizer took an average of %.1f us%n", time / runs / 1000.0);
}
{
long start = System.nanoTime();
Pattern spacePattern = Pattern.compile(" ");
for (int r = 0; r < runs; r++) {
List<String> list = Arrays.asList(spacePattern.split(sample, 0));
}
long time = System.nanoTime() - start;
System.out.printf("Pattern.split took an average of %.1f us%n", time / runs / 1000.0);
}
{
long start = System.nanoTime();
for (int r = 0; r < runs; r++) {
List<String> list = new ArrayList<String>();
int pos = 0, end;
while ((end = sample.indexOf('' '', pos)) >= 0) {
list.add(sample.substring(pos, end));
pos = end + 1;
}
}
long time = System.nanoTime() - start;
System.out.printf("indexOf loop took an average of %.1f us%n", time / runs / 1000.0);
}
}
huellas dactilares
StringTokenizer took an average of 5.8 us
Pattern.split took an average of 4.8 us
indexOf loop took an average of 1.8 us
StringTokenizer took an average of 4.9 us
Pattern.split took an average of 3.7 us
indexOf loop took an average of 1.7 us
StringTokenizer took an average of 5.2 us
Pattern.split took an average of 3.9 us
indexOf loop took an average of 1.8 us
StringTokenizer took an average of 5.1 us
Pattern.split took an average of 4.1 us
indexOf loop took an average of 1.6 us
StringTokenizer took an average of 5.0 us
Pattern.split took an average of 3.8 us
indexOf loop took an average of 1.6 us
El costo de abrir un archivo será de aproximadamente 8 ms. Como los archivos son tan pequeños, su caché puede mejorar el rendimiento en un factor de 2-5x. Aun así va a pasar ~ 10 horas abriendo archivos. El costo de usar split vs StringTokenizer es mucho menor a 0.01 ms cada uno. Para analizar 19 millones de palabras x 30 *, 8 letras por palabra deberían tomar alrededor de 10 segundos (aproximadamente 1 GB por 2 segundos)
Si desea mejorar el rendimiento, le sugiero que tenga muchos menos archivos. por ejemplo, usar una base de datos. Si no quiere usar una base de datos SQL, sugiero usar uno de estos http://nosql-database.org/
Split en Java 7 simplemente llama a indexOf para esta entrada, vea la fuente . Split debería ser muy rápido, cerca de llamadas repetidas de indexOf.
Usa split
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 división en su lugar.