En Java, ¿cuál sería la forma más rápida de iterar sobre todos los caracteres en una Cadena, esto:
String str = "a really, really long string";
for (int i = 0, n = str.length(); i < n; i++) {
char c = str.charAt(i);
}
O esto:
char[] chars = str.toCharArray();
for (int i = 0, n = chars.length; i < n; i++) {
char c = chars[i];
}
EDITAR:
Lo que me gustaría saber es si el costo de llamar repetidamente al charAt
método durante una iteración larga termina siendo menor o mayor que el costo de realizar una sola llamada al toCharArray
principio y luego acceder directamente a la matriz durante la iteración.
Sería genial si alguien pudiera proporcionar un punto de referencia sólido para diferentes longitudes de cadena, teniendo en cuenta el tiempo de calentamiento de JIT, el tiempo de inicio de JVM, etc. y no solo la diferencia entre dos llamadas a System.currentTimeMillis()
.
for (char c : chars)
?charAt
termina siendo menor o mayor que el costo de realizar una sola llamada atoCharArray
Respuestas:
PRIMERA ACTUALIZACIÓN: antes de intentar esto en un entorno de producción (no recomendado), lea esto primero: http://www.javaspecialists.eu/archive/Issue237.html A partir de Java 9, la solución descrita ya no funcionará , porque ahora Java almacenará cadenas como byte [] de forma predeterminada.
SEGUNDA ACTUALIZACIÓN: A partir del 25/10/2016, en mi AMDx64 8core y fuente 1.8, no hay diferencia entre usar 'charAt' y acceso de campo. Parece que jvm está lo suficientemente optimizado para en línea y racionalizar cualquier llamada 'string.charAt (n)'.
Todo depende de la duración de la
String
inspección. Si, como dice la pregunta, es para cadenas largas , la forma más rápida de inspeccionar la cadena es usar la reflexión para acceder al respaldochar[]
de la cadena.Un punto de referencia completamente aleatorio con JDK 8 (win32 y win64) en un 64 AMD Phenom II 4 core 955 @ 3.2 GHZ (tanto en modo cliente como en modo servidor) con 9 técnicas diferentes (¡ver más abajo!) Muestra que usar
String.charAt(n)
es el más rápido para pequeños cadenas y que usarreflection
para acceder a la matriz de respaldo de cadenas es casi el doble de rápido para cadenas grandes.EL EXPERIMENTO
Se prueban 9 técnicas diferentes de optimización.
Todos los contenidos de la cadena son aleatorios
La prueba se realiza para tamaños de cuerda en múltiplos de dos, comenzando con 0,1,2,4,8,16, etc.
Las pruebas se realizan 1,000 veces por tamaño de cadena
Las pruebas se barajan en orden aleatorio cada vez. En otras palabras, las pruebas se realizan en orden aleatorio cada vez que se realizan, más de 1000 veces.
Todo el conjunto de pruebas se realiza hacia adelante y hacia atrás para mostrar el efecto del calentamiento de JVM en la optimización y los tiempos.
Toda la suite se realiza dos veces, una en
-client
modo y la otra en-server
modo.CONCLUSIONES
-modo de cliente (32 bits)
Para cadenas de 1 a 256 caracteres de longitud , las llamadas
string.charAt(i)
ganan con un procesamiento promedio de 13.4 millones a 588 millones de caracteres por segundo.Además, en general es 5.5% más rápido (cliente) y 13.9% (servidor) así:
que así con una variable de longitud final local:
Para cadenas largas, de 512 a 256K caracteres de longitud , usar la reflexión para acceder a la matriz de respaldo de la cadena es más rápido. Esta técnica es casi el doble de rápida que String.charAt (i) (178% más rápido). La velocidad promedio en este rango fue de 1.111 mil millones de caracteres por segundo.
El campo debe obtenerse con anticipación y luego puede reutilizarse en la biblioteca en diferentes cadenas. Curiosamente, a diferencia del código anterior, con acceso de campo, es un 9% más rápido tener una variable de longitud final local que usar 'chars.length' en la verificación de bucle. Así es como el acceso de campo se puede configurar como más rápido:
Comentarios especiales sobre el modo servidor
El acceso de campo comienza a ganar después de cadenas de 32 caracteres en modo servidor en una máquina Java de 64 bits en mi máquina AMD 64. Eso no se vio hasta 512 caracteres de longitud en modo cliente.
También vale la pena señalar que creo que cuando estaba ejecutando JDK 8 (compilación de 32 bits) en modo servidor, el rendimiento general fue un 7% más lento para cadenas grandes y pequeñas. Esto fue con la versión 121 de diciembre de 2013 de la versión temprana de JDK 8. Entonces, por ahora, parece que el modo de servidor de 32 bits es más lento que el modo de cliente de 32 bits.
Dicho esto ... parece que el único modo de servidor que vale la pena invocar es en una máquina de 64 bits. De lo contrario, en realidad obstaculiza el rendimiento.
Para la compilación de 32 bits que se ejecuta en
-server mode
un AMD64, puedo decir esto:También vale la pena decir que String.chars () (Stream y la versión paralela) son un fracaso. Mucho más lento que cualquier otro. La
Streams
API es una forma bastante lenta de realizar operaciones generales de cadena.Lista de deseos
Java String podría tener un predicado que acepte métodos optimizados tales como contiene (predicado), forEach (consumer), forEachWithIndex (consumer). Por lo tanto, sin la necesidad de que el usuario conozca la duración o repita las llamadas a los métodos String, estos podrían ayudar a analizar las bibliotecas
beep-beep beep
acelerando.Sigue soñando :)
Happy Strings!
~ SH
La prueba utilizó los siguientes 9 métodos para probar la cadena en busca de espacios en blanco:
"charAt1" - COMPRUEBE EL CONTENIDO DE LA CADENA DE LA MANERA habitual:
"charAt2" - LO MISMO QUE ARRIBA PERO UTILICE String.length () EN LUGAR DE HACER UNA INT. LOCAL FINAL POR LA LONGITUD
"stream" - USE EL NUEVO IntStream de String JAVA-8 Y PÁGELO PREDICADO PARA HACER LA COMPROBACIÓN
"streamPara" - LO MISMO QUE ARRIBA, PERO OH-LA-LA - ¡¡VAYA PARALELO !!!
"reutilizar" - RELLENE UN CARGADOR REUTILIZABLE [] CON EL CONTENIDO DE LAS CUERDAS
"new1" - OBTENGA UNA NUEVA COPIA DEL CHAR [] DESDE LA CADENA
"new2" - IGUAL QUE ARRIBA, PERO USE "PARA CADA"
"campo1" - FANCY !! OBTENGA CAMPO PARA ACCEDER AL CHAR INTERNO DE LA CADENA []
"field2" - MISMO QUE ARRIBA, PERO USE "PARA CADA"
RESULTADOS COMPUESTOS PARA
-client
MODO CLIENTE (pruebas de avance y retroceso combinadas)Nota: que el modo de cliente con Java de 32 bits y el modo de servidor con Java de 64 bits son los mismos que a continuación en mi máquina AMD64.
RESULTADOS COMPUESTOS PARA EL
-server
MODO DE SERVIDOR (pruebas de avance y retroceso combinadas)Nota: esta es la prueba para Java 32 bits que se ejecuta en modo servidor en un AMD64. El modo de servidor para Java de 64 bits era el mismo que el de Java de 32 bits en modo cliente, excepto que el acceso de campo comenzó a ganar después del tamaño de 32 caracteres.
CÓDIGO DE PROGRAMA EJECUTABLE COMPLETO
(para probar en Java 7 y versiones anteriores, elimine las dos pruebas de secuencias)
fuente
Esto no es más que una microoptimización de la que no debe preocuparse.
le devuelve una copia de
str
matrices de caracteres (en JDK, devuelve una copia de caracteres llamandoSystem.arrayCopy
).Aparte de eso,
str.charAt()
solo verifica si el índice está realmente dentro de los límites y devuelve un carácter dentro del índice de la matriz.El primero no crea memoria adicional en JVM.
fuente
Solo por curiosidad y para comparar con la respuesta de Saint Hill.
Si necesita procesar datos pesados, no debe usar JVM en modo cliente. El modo cliente no está hecho para optimizaciones.
Comparemos los resultados de los puntos de referencia de @Saint Hill utilizando una JVM en modo Cliente y modo Servidor.
Ver también: ¿ Diferencias reales entre "java -server" y "java -client"?
MODO CLIENTE:
MODO DE SERVIDOR:
CONCLUSIÓN:
Como puede ver, el modo servidor es mucho más rápido.
fuente
El primero que use
str.charAt
debería ser más rápido.Si cava dentro del código fuente de la
String
clase, podemos ver quecharAt
se implementa de la siguiente manera:Aquí, todo lo que hace es indexar una matriz y devolver el valor.
Ahora, si vemos la implementación de
toCharArray
, encontraremos lo siguiente:Como puede ver, está haciendo algo
System.arraycopy
que definitivamente va a ser un poco más lento que no hacerlo.fuente
A pesar de la respuesta de @Saint Hill si considera la complejidad temporal de str.toCharArray () ,
el primero es más rápido incluso para cadenas muy grandes. Puede ejecutar el siguiente código para verlo usted mismo.
salida:
fuente
Parece que niether es más rápido o más lento
Para cadenas largas, elegiré el primero. ¿Por qué copiar cadenas largas? La documentación dice:
// Editar 1
He cambiado la prueba para engañar a la optimización JIT.
// Editar 2
Repita la prueba 10 veces para dejar que JVM se caliente.
// Editar 3
Conclusiones:
En primer lugar,
str.toCharArray();
copia la cadena completa en la memoria. Puede consumir memoria para cadenas largas. El métodoString.charAt( )
busca char en una matriz de caracteres dentro del índice de comprobación de la clase String antes Parece que el primer método de Strings lo suficientemente corto (es decir,chatAt
método) es un poco más lento debido a esta comprobación de índice. Pero si la cadena es lo suficientemente larga, la copia de toda la matriz de caracteres se vuelve más lenta y el primer método es más rápido. Cuanto más larga es la cadena, más lento setoCharArray
realiza. Intenta cambiar el límite enfor(int j = 0; j < 10000; j++)
bucle para verlo. Si dejamos que el código de calentamiento JVM se ejecute más rápido, pero las proporciones son las mismas.Después de todo, es solo micro-optimización.
fuente
for:in
opción, solo por el gusto de hacerlo?Iterable
ni matriz.String.toCharArray()
crea una nueva matriz de caracteres, significa la asignación de memoria de la longitud de la cadena, luego copia la matriz de caracteres original de la cadena utilizandoSystem.arraycopy()
y luego devuelve esta copia a la persona que llama. String.charAt () devuelve el carácter en la posicióni
de la copia original, por esoString.charAt()
será más rápido queString.toCharArray()
. Sin embargo,String.toCharArray()
devuelve una copia y no un carácter de la matriz de cadenas original, dondeString.charAt()
devuelve el carácter de la matriz de caracteres original. El siguiente código devuelve el valor en el índice especificado de esta cadena.el siguiente código devuelve una matriz de caracteres recién asignada cuya longitud es la longitud de esta cadena
fuente
El segundo hace que se cree una nueva matriz de caracteres, y todos los caracteres de la Cadena se copien en esta nueva matriz de caracteres, por lo que supongo que el primero es más rápido (y requiere menos memoria).
fuente