Desempenho da class StringTokenizer vs. o método String.split em Java

No meu software eu preciso dividir a string em palavras. Atualmente tenho mais de 19.000.000 de documentos com mais de 30 palavras cada.

Qual das duas maneiras a seguir é a melhor maneira de fazer isso (em termos de desempenho)?

StringTokenizer sTokenize = new StringTokenizer(s," "); while (sTokenize.hasMoreTokens()) { 

ou

 String[] splitS = s.split(" "); for(int i =0; i < splitS.length; i++) 

Se seus dados já estiverem em um database, você precisará analisar a cadeia de palavras, sugiro usar indexOf repetidamente. É muitas vezes mais rápido que qualquer solução.

No entanto, obter os dados de um database ainda é muito mais caro.

 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 list = new ArrayList(); 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 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 list = new ArrayList(); 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); } } 

impressões

 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 

O custo de abrir um arquivo será de aproximadamente 8 ms. Como os arquivos são muito pequenos, seu cache pode melhorar o desempenho por um fator de 2-5x. Mesmo assim vai gastar ~ 10 horas abrindo arquivos. O custo de usar o split vs StringTokenizer é muito menor que 0,01 ms cada. Para analisar 19 milhões x 30 palavras * 8 letras por palavra devem levar cerca de 10 segundos (a cerca de 1 GB por 2 segundos)

Se você quiser melhorar o desempenho, sugiro que você tenha muito menos arquivos. Por exemplo, use um database. Se você não quiser usar um database SQL, sugiro usar um desses http://nosql-database.org/

A divisão no Java 7 apenas chama indexOf para essa input, consulte a origem . A divisão deve ser muito rápida, perto de chamadas repetidas de indexOf.

A especificação da API Java recomenda o uso de split . Veja a documentação do StringTokenizer .

Outra coisa importante, não documentada até onde notei, é que pedir ao StringTokenizer para retornar os delimitadores junto com a string StringTokenizer(String str, String delim, boolean returnDelims) usando o construtor StringTokenizer(String str, String delim, boolean returnDelims) ) também reduz o tempo de processamento. Então, se você está procurando por desempenho, eu recomendaria 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; } 

Apesar do overhead introduzido pelo método getNext (), que descarta os delimitadores para você, ainda é 50% mais rápido de acordo com meus benchmarks.

Use divisão.

StringTokenizer é uma class herdada que é retida por motivos de compatibilidade, embora seu uso seja desencorajado em um novo código. Recomenda-se que qualquer pessoa que busque essa funcionalidade use o método de divisão.

O que os 19.000.000 de documentos precisam fazer lá? Você tem que dividir palavras em todos os documentos regularmente? Ou é um problema de um tiro?

Se você exibir / solicitar um documento de cada vez, com apenas 30 palavras, esse é um problema tão pequeno que qualquer método funcionaria.

Se você tiver que processar todos os documentos de uma só vez, com apenas 30 palavras, esse é um problema tão pequeno que é mais provável que você seja vinculado a E / S de qualquer maneira.

Ao executar micro (e neste caso, mesmo nano) benchmarks, há muita coisa que afeta seus resultados. Otimizações JIT e garbage collection, para citar apenas alguns.

Para obter resultados significativos dos micro benchmarks, confira a biblioteca jmh . Tem excelentes amostras incluídas em como executar bons benchmarks.

Independentemente do seu status legado, eu esperaria que o StringTokenizer fosse significativamente mais rápido do que o String.split() para essa tarefa, porque ele não usa expressões regulares: ele varre a input diretamente, da mesma forma que você faria através do indexOf() . Na verdade String.split() tem que compilar o regex toda vez que você chamá-lo, por isso não é tão eficiente quanto usar uma expressão regular diretamente a si mesmo.

Este poderia ser um benchmarking razoável usando 1.6.0

 http://www.javamex.com/tutorials/regular_expressions/splitting_tokenisation_performance.shtml#.V6-CZvnhCM8 

O PerformanceString é melhor do que o split. Verifique o código abaixo

insira a descrição da imagem aqui

Mas de acordo com documentos Java, seu uso é desencorajado. Verifique aqui