2012-06-12 25 views
29

Aquí está el código actual en mi solicitud:actuaciones Separar una cadena Java

String[] ids = str.split("/"); 

Al perfilar la aplicación, he notado que un tiempo no despreciable se gasta para dividir la cadena.

También me enteré de que split en realidad toma una expresión regular, lo cual es inútil para mí aquí.

Así que mi pregunta es, ¿Qué alternativa puedo usar para optimizar la división de cuerdas? He visto StringUtils.split pero ¿es más rápido?

(Me hubiera probado a mí mismo, pero perfilar mi aplicación requiere mucho tiempo, así que si alguien ya sabe la respuesta que es un poco de tiempo ahorrado)

Respuesta

32

String.split(String) no creará expresiones regulares si su patrón tiene solo un carácter. Al dividir por carácter individual, utilizará un código especializado que es bastante eficiente. StringTokenizer no es mucho más rápido en este caso particular.

Esto se introdujo en OpenJDK7/OracleJDK7. Here's a bug report y a commit.He hecho un simple benchmark here.


$ java -version 
java version "1.8.0_20" 
Java(TM) SE Runtime Environment (build 1.8.0_20-b26) 
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode) 

$ java Split 
split_banthar: 1231 
split_tskuzzy: 1464 
split_tskuzzy2: 1742 
string.split: 1291 
StringTokenizer: 1517 
+1

gracias por este punto de referencia. Sin embargo, su código es "injusto" ya que la parte de StringTokenizer evita crear una lista y convertirla en una matriz ... ¡pero es un gran punto de partida! –

7

StringTokenizer es mucho más rápido para los simples análisis como este (I Hizo algunas evaluaciones comparativas con hace un tiempo y se obtienen grandes aceleraciones).

StringTokenizer st = new StringTokenizer("1/2/3","/"); 
String[] arr = st.countTokens(); 
arr[0] = st.nextToken(); 

Si desea eek un poco más el rendimiento, puede hacerlo de forma manual, así:

String s = "1/2/3" 
char[] c = s.toCharArray(); 
LinkedList<String> ll = new LinkedList<String>(); 
int index = 0; 

for(int i=0;i<c.length;i++) { 
    if(c[i] == '/') { 
     ll.add(s.substring(index,i)); 
     index = i+1; 
    } 
} 

String[] arr = ll.size(); 
Iterator<String> iter = ll.iterator(); 
index = 0; 

for(index = 0; iter.hasNext(); index++) 
    arr[index++] = iter.next(); 
+4

StringTokenizer es una clase de legado que es retenido 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 de String o el paquete java.util.regex en su lugar. –

+3

El hecho de que sea un legado no significa que no sea útil.Y, de hecho, esta clase en particular es realmente muy útil para ese aumento de rendimiento adicional, así que estoy realmente en contra de esta etiqueta "heredada". – tskuzzy

+3

El método de división de 'String' y el paquete' java.util.regex' implica la sobrecarga significativa de usar expresiones regulares. 'StringTokenizer' no. –

3

java.util.StringTokenizer(String str, String delim) es dos veces más rápido acuerdo con this post.

Sin embargo, a menos que su aplicación sea de una escala gigantesca, split debería estar bien para usted (c.f. misma publicación, cita miles de cadenas en unos pocos milisegundos).

+0

no requiere una aplicación a gran escala, una división en un circuito cerrado como un analizador de documentos es suficiente -y frecuente- Piensa en rutinas típicas de análisis de enlaces de Twitter, correos electrónicos, hashtags ... Se alimentan con Mb de texto para analizar. La rutina en sí puede tener unas pocas docenas de líneas, pero se llamará cientos de veces por segundo. – rupps

15

Si puede utilizar librerías de terceros, Guava'sSplitter no incurre en la sobrecarga de expresiones regulares cuando no lo solicita, y es muy rápido como regla general. (Revelación: I contribuir a guayaba.)

Iterable<String> split = Splitter.on('/').split(string); 

(También, Splitter es como regla much more predictable de String.split.)

+1

Esto hizo una diferencia muy significativa para mí al usarlo en las líneas de un archivo grande. –

+2

Este artículo recomienda el no uso de Iterable, incluso el jefe del equipo de Guava lo dice ... http: //alexruiz.developerblogs.com/? P = 2519 – sirvon

1

Guava tiene una Splitter que es más flexible que el método String.split(), y no lo hace (necesariamente) use una expresión regular. OTOH, String.split() se ha optimizado en Java 7 para evitar la maquinaria de expresiones regulares si el separador es un solo carácter. Por lo tanto, el rendimiento debería ser similar en Java 7.

+0

Oh OK, estoy usando Java 5 (desafortunadamente sí, no puedo cambiar eso) –

0

El método de división de String es probablemente una opción más segura. As of at least java 6 (aunque la referencia api es para 7), básicamente dicen que se desaconseja el uso de StringTokenizer. Su redacción se cita a continuación.

"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 split de String o el paquete java.util.regex. "

2

StringTokenizer es más rápido que cualquier otro método de división, pero conseguir la tokenizer para devolver los delimitadores junto con la cadena tokenized mejora el rendimiento de algo así como el 50%. Esto se logra usando el constructor java.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims). Aquí algunas otras ideas al respecto: Performance of StringTokenizer class vs. split method in Java

0

Puede escribir la función de división usted mismo, que será la más rápida. Aquí está el enlace que lo demuestra, que trabajó para mí también, optimizado mi código por 6X

StringTokenizer - reading lines with integers

de Split: 366ms IndexOf: 50 ms StringTokenizer: 89ms GuavaSplit: 109ms IndexOf2 (algunos súper optimizado solución dada en la pregunta anterior): 14ms CsvMapperSplit (mapeo fila por fila): 326ms CsvMapperSplit_DOC (construcción de uno doc y el mapeo de todas las filas de una sola vez): 177ms

2

Viendo como estoy trabajando en gran escala, que t Pensé que ayudaría a proporcionar más benchmarking, incluyendo algunas de mis propias implementaciones (me divido en espacios, pero esto debería ilustrar cuánto tiempo lleva en general):

Estoy trabajando con un archivo de 426 MB, con 2622761 líneas. Los únicos espacios en blanco son espacios normales ("") y líneas ("\ n").

Primera puedo reemplazar todas las líneas con espacios, y el punto de referencia de análisis de una enorme línea:

.split(" ") 
Cumulative time: 31.431366952 seconds 

.split("\s") 
Cumulative time: 52.948729489 seconds 

splitStringChArray() 
Cumulative time: 38.721338004 seconds 

splitStringChList() 
Cumulative time: 12.716065893 seconds 

splitStringCodes() 
Cumulative time: 1 minutes, 21.349029036000005 seconds 

splitStringCharCodes() 
Cumulative time: 23.459840685 seconds 

StringTokenizer 
Cumulative time: 1 minutes, 11.501686094999997 seconds 

Entonces línea de separación de referencia en línea (lo que significa que las funciones y los bucles se hacen muchas veces, en lugar de todos a la vez):

.split(" ") 
Cumulative time: 3.809014174 seconds 

.split("\s") 
Cumulative time: 7.906730124 seconds 

splitStringChArray() 
Cumulative time: 4.06576739 seconds 

splitStringChList() 
Cumulative time: 2.857809996 seconds 

Bonus: splitStringChList(), but creating a new StringBuilder every time (the average difference is actually more like .42 seconds): 
Cumulative time: 3.82026621 seconds 

splitStringCodes() 
Cumulative time: 11.730249921 seconds 

splitStringCharCodes() 
Cumulative time: 6.995555826 seconds 

StringTokenizer 
Cumulative time: 4.500008172 seconds 

Aquí está el código:

// Use a char array, and count the number of instances first. 
public static String[] splitStringChArray(String str, StringBuilder sb) { 
    char[] strArray = str.toCharArray(); 
    int count = 0; 
    for (char c : strArray) { 
     if (c == ' ') { 
      count++; 
     } 
    } 
    String[] splitArray = new String[count+1]; 
    int i=0; 
    for (char c : strArray) { 
     if (c == ' ') { 
      splitArray[i] = sb.toString(); 
      sb.delete(0, sb.length()); 
     } else { 
      sb.append(c); 
     } 
    } 
    return splitArray; 
} 

// Use a char array but create an ArrayList, and don't count beforehand. 
public static ArrayList<String> splitStringChList(String str, StringBuilder sb) { 
    ArrayList<String> words = new ArrayList<String>(); 
    words.ensureCapacity(str.length()/5); 
    char[] strArray = str.toCharArray(); 
    int i=0; 
    for (char c : strArray) { 
     if (c == ' ') { 
      words.add(sb.toString()); 
      sb.delete(0, sb.length()); 
     } else { 
      sb.append(c); 
     } 
    } 
    return words; 
} 

// Using an iterator through code points and returning an ArrayList. 
public static ArrayList<String> splitStringCodes(String str) { 
    ArrayList<String> words = new ArrayList<String>(); 
    words.ensureCapacity(str.length()/5); 
    IntStream is = str.codePoints(); 
    OfInt it = is.iterator(); 
    int cp; 
    StringBuilder sb = new StringBuilder(); 
    while (it.hasNext()) { 
     cp = it.next(); 
     if (cp == 32) { 
      words.add(sb.toString()); 
      sb.delete(0, sb.length()); 
     } else { 
      sb.append(cp); 
     } 
    } 

    return words; 
} 

// This one is for compatibility with supplementary or surrogate characters (by using Character.codePointAt()) 
public static ArrayList<String> splitStringCharCodes(String str, StringBuilder sb) { 
    char[] strArray = str.toCharArray(); 
    ArrayList<String> words = new ArrayList<String>(); 
    words.ensureCapacity(str.length()/5); 
    int cp; 
    int len = strArray.length; 
    for (int i=0; i<len; i++) { 
     cp = Character.codePointAt(strArray, i); 
     if (cp == ' ') { 
      words.add(sb.toString()); 
      sb.delete(0, sb.length()); 
     } else { 
      sb.append(cp); 
     } 
    } 

    return words; 
} 

Esta es la forma Solía ​​StringTokenizer:

StringTokenizer tokenizer = new StringTokenizer(file.getCurrentString()); 
    words = new String[tokenizer.countTokens()]; 
    int i = 0; 
    while (tokenizer.hasMoreTokens()) { 
     words[i] = tokenizer.nextToken(); 
     i++; 
    } 
0

Uso de Apache Commons Lang »3.0 's

StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"] 

Si necesita dividida no de expresiones regulares y quiere que los resultados en conjunto de cuerdas, a continuación, utilizar StringUtils, comparé con StringUtils.splitByWholeSeparator El Splitter de Guava y el String de Java se dividen, y descubrieron que StringUtils es más rápido.

  1. StringUtils - 8 ms
  2. String - 11 ms
  3. splitter - 1 ms (pero devuelve Iterable/Iterator y su conversión a matriz de cadenas toma total de 54ms)
Cuestiones relacionadas