En Java, quel serait le moyen le plus rapide d'itérer sur tous les caractères d'une chaîne, ceci:
String str = "a really, really long string";
for (int i = 0, n = str.length(); i < n; i++) {
char c = str.charAt(i);
}
Ou ca:
char[] chars = str.toCharArray();
for (int i = 0, n = chars.length; i < n; i++) {
char c = chars[i];
}
ÉDITER :
Ce que j'aimerais savoir, c'est si le coût d'appels répétés de la charAt
méthode pendant une longue itération finit par être inférieur ou supérieur au coût d'un seul appel toCharArray
au début, puis d'accéder directement au tableau pendant l'itération.
Ce serait formidable si quelqu'un pouvait fournir une référence robuste pour différentes longueurs de chaîne, en tenant compte du temps de préchauffage JIT, du temps de démarrage de la JVM, etc. et pas seulement de la différence entre deux appels à System.currentTimeMillis()
.
for (char c : chars)
?charAt
finit par être inférieur ou supérieur au coût d'un seul appel àtoCharArray
Réponses:
PREMIÈRE MISE À JOUR: Avant d'essayer cela dans un environnement de production (déconseillé), lisez d'abord ceci: http://www.javaspecialists.eu/archive/Issue237.html À partir de Java 9, la solution décrite ne fonctionnera plus , car maintenant Java stockera les chaînes sous forme d'octet [] par défaut.
DEUXIÈME MISE À JOUR: À partir du 25/10/2016, sur mon AMDx64 8core et la source 1.8, il n'y a aucune différence entre l'utilisation de 'charAt' et l'accès au champ. Il semble que le jvm soit suffisamment optimisé pour intégrer et rationaliser tous les appels 'string.charAt (n)'.
Tout dépend de la durée de l'
String
inspection. Si, comme le dit la question, il s'agit de longues chaînes, le moyen le plus rapide d'inspecter la chaîne est d'utiliser la réflexion pour accéder au supportchar[]
de la chaîne.Un benchmark entièrement randomisé avec JDK 8 (win32 et win64) sur un AMD Phenom II 4 core 955 @ 3.2 GHZ (en mode client et en mode serveur) avec 9 techniques différentes (voir ci-dessous!) Montre que l'utilisation
String.charAt(n)
est la plus rapide pour les petits strings et que l'utilisationreflection
pour accéder au tableau de sauvegarde String est presque deux fois plus rapide pour les grandes chaînes.L'EXPÉRIENCE
9 techniques d'optimisation différentes sont essayées.
Tout le contenu de la chaîne est aléatoire
Les tests sont effectués pour des tailles de chaîne par multiples de deux commençant par 0,1,2,4,8,16 etc.
Les tests sont effectués 1000 fois par taille de chaîne
Les tests sont mélangés dans un ordre aléatoire à chaque fois. En d'autres termes, les tests sont effectués dans un ordre aléatoire à chaque fois qu'ils sont effectués, plus de 1000 fois.
L'ensemble de la suite de tests est effectué en avant et en arrière pour montrer l'effet du préchauffage de la JVM sur l'optimisation et les temps.
L'ensemble de la suite se fait deux fois, une fois en
-client
mode et l'autre en-server
mode.CONCLUSIONS
-mode client (32 bits)
Pour les chaînes de 1 à 256 caractères , l'appel l'
string.charAt(i)
emporte avec un traitement moyen de 13,4 millions à 588 millions de caractères par seconde.En outre, il est globalement 5,5% plus rapide (client) et 13,9% (serveur) comme ceci:
que comme ça avec une variable de longueur finale locale:
Pour les chaînes longues, d'une longueur de 512 à 256K caractères , l'utilisation de la réflexion pour accéder au tableau de sauvegarde de String est la plus rapide. Cette technique est presque deux fois plus rapide que String.charAt (i) (178% plus rapide). La vitesse moyenne sur cette plage était de 1,111 milliard de caractères par seconde.
Le champ doit être obtenu à l'avance, puis il peut être réutilisé dans la bibliothèque sur différentes chaînes. Fait intéressant, contrairement au code ci-dessus, avec l'accès au champ, il est 9% plus rapide d'avoir une variable de longueur finale locale que d'utiliser 'chars.length' dans la vérification de la boucle. Voici comment l'accès au champ peut être configuré le plus rapidement:
Commentaires spéciaux sur le mode -server
L'accès aux champs commence à gagner après des chaînes de 32 caractères en mode serveur sur une machine Java 64 bits sur ma machine AMD 64. Cela n'a pas été vu avant une longueur de 512 caractères en mode client.
Il convient également de noter que lorsque j'exécutais JDK 8 (version 32 bits) en mode serveur, les performances globales étaient 7% plus lentes pour les grandes et les petites chaînes. C'était avec la build 121 décembre 2013 de la version anticipée du JDK 8. Donc, pour l'instant, il semble que le mode serveur 32 bits soit plus lent que le mode client 32 bits.
Cela étant dit ... il semble que le seul mode serveur qui mérite d'être invoqué soit sur une machine 64 bits. Sinon, cela nuit en fait aux performances.
Pour une version 32 bits fonctionnant
-server mode
sur un AMD64, je peux dire ceci:Il convient également de dire que String.chars () (Stream et la version parallèle) sont un buste. Bien plus lent que tout autre moyen. L'
Streams
API est un moyen plutôt lent d'effectuer des opérations générales sur les chaînes.Liste de souhaits
Java String peut avoir un prédicat acceptant des méthodes optimisées telles que contains (prédicat), forEach (consommateur), forEachWithIndex (consumer). Ainsi, sans que l'utilisateur ait besoin de connaître la longueur ou de répéter les appels aux méthodes String, celles-ci pourraient
beep-beep beep
accélérer l' analyse des bibliothèques .Continue de rêver :)
Joyeuses cordes!
~ SH
Le test a utilisé les 9 méthodes suivantes pour tester la chaîne pour la présence d'espaces blancs:
"charAt1" - VÉRIFIEZ LE CONTENU DE LA CHAÎNE DE LA MANIÈRE HABITUELLE:
"charAt2" - MÊME QUE CI-DESSUS, MAIS UTILISEZ String.length () AU LIEU DE FAIRE UN INT LOCAL FINAL POUR LA LONGUEUR
"stream" - UTILISEZ LE NOUVEAU JAVA-8 String's IntStream ET PASSEZ-LE UN PRÉDICAT POUR FAIRE LA VÉRIFICATION
"streamPara" - MÊME QUE CI-DESSUS, MAIS OH-LA-LA - ALLEZ PARALLÈLE !!!
"réutiliser" - REMPLIR UN CARACTERISTIQUE RÉUTILISABLE [] AVEC LES CHAÎNES CONTENU
"new1" - OBTENEZ UNE NOUVELLE COPIE DU caractère [] DE LA CHAÎNE
"new2" - MÊME QUE CI-DESSUS, MAIS UTILISEZ "FOR-EACH"
"field1" - FANTAISIE !! OBTENIR UN CHAMP POUR ACCÉDER AU caractère INTERNE DE LA STRING []
"field2" - MÊME QUE CI-DESSUS, MAIS UTILISEZ "FOR-EACH"
RÉSULTATS COMPOSITES POUR LE
-client
MODE CLIENT (tests avant et arrière combinés)Remarque: que le mode -client avec Java 32 bits et le mode -server avec Java 64 bits sont les mêmes que ci-dessous sur ma machine AMD64.
RÉSULTATS COMPOSITES POUR LE
-server
MODE SERVEUR (tests avant et arrière combinés)Remarque: il s'agit du test de Java 32 bits fonctionnant en mode serveur sur un AMD64. Le mode serveur pour Java 64 bits était le même que pour Java 32 bits en mode client, sauf que l'accès aux champs commençait à gagner après une taille de 32 caractères.
CODE DE PROGRAMME COMPLET EXÉCUTABLE
(pour tester sur Java 7 et versions antérieures, supprimez les tests des deux flux)
la source
Il ne s'agit que d'une micro-optimisation dont vous ne devriez pas vous inquiéter.
vous renvoie une copie des
str
tableaux de caractères (en JDK, il renvoie une copie des caractères en appelantSystem.arrayCopy
).En dehors de cela,
str.charAt()
vérifie uniquement si l'index est effectivement dans les limites et renvoie un caractère dans l'index du tableau.Le premier ne crée pas de mémoire supplémentaire dans JVM.
la source
Juste pour la curiosité et pour comparer avec la réponse de Saint Hill.
Si vous avez besoin de traiter des données lourdes, vous ne devez pas utiliser JVM en mode client. Le mode client n'est pas fait pour les optimisations.
Comparons les résultats des benchmarks @Saint Hill en utilisant une JVM en mode Client et en mode Serveur.
Voir aussi: De vraies différences entre "java -server" et "java -client"?
MODE CLIENT:
MODE SERVEUR:
CONCLUSION:
Comme vous pouvez le voir, le mode serveur est beaucoup plus rapide.
la source
Le premier à utiliser
str.charAt
devrait être plus rapide.Si vous creusez à l'intérieur du code source de la
String
classe, nous pouvons voir qu'ilcharAt
est implémenté comme suit:Ici, il ne fait qu'indexer un tableau et renvoyer la valeur.
Maintenant, si nous voyons l'implémentation de
toCharArray
, nous trouverons ci-dessous:Comme vous le voyez, il fait un
System.arraycopy
qui va certainement être un peu plus lent que de ne pas le faire.la source
Malgré la réponse de @Saint Hill si vous considérez la complexité temporelle de str.toCharArray () ,
le premier est plus rapide même pour les très grosses cordes. Vous pouvez exécuter le code ci-dessous pour le voir par vous-même.
production:
la source
On dirait que niether est plus rapide ou plus lent
Pour les longues chaînes, j'ai choisi le premier. Pourquoi copier autour de longues chaînes? Documentations dit:
// Modifier 1
J'ai changé le test pour tromper l'optimisation JIT.
// Modifier 2
Répétez le test 10 fois pour laisser la JVM se réchauffer.
// Modifier 3
Conclusions:
Tout d'abord
str.toCharArray();
copie la chaîne entière en mémoire. Cela peut consommer de la mémoire pour les longues chaînes. La méthodeString.charAt( )
recherche avant le char dans le tableau char à l'intérieur de la classe String vérifiant l'index. Il semble que pour les chaînes assez courtes, la première méthode (c'est-à-dire lachatAt
méthode) est un peu plus lente à cause de cette vérification d'index. Mais si la chaîne est suffisamment longue, la copie du tableau entier de caractères devient plus lente et la première méthode est plus rapide. Plus la corde est longue, plus les performances sont lentestoCharArray
. Essayez de changer la limite enfor(int j = 0; j < 10000; j++)
boucle pour le voir. Si nous laissons JVM préchauffer, le code s'exécute plus rapidement, mais les proportions sont les mêmes.Après tout, ce n'est que de la micro-optimisation.
la source
for:in
option, juste pour le plaisir?Iterable
ni array.String.toCharArray()
crée un nouveau tableau de caractères, signifie l'allocation de mémoire de longueur de chaîne, puis copie le tableau de caractères d'origine de la chaîne en utilisantSystem.arraycopy()
, puis renvoie cette copie à l'appelant. String.charAt () renvoie le caractère à la positioni
de la copie originale, c'est pourquoiString.charAt()
sera plus rapide queString.toCharArray()
. Bien que,String.toCharArray()
renvoie une copie et non un caractère du tableau String d'origine, oùString.charAt()
renvoie le caractère du tableau de caractères d'origine. Le code ci-dessous renvoie la valeur à l'index spécifié de cette chaîne.code ci-dessous renvoie un tableau de caractères nouvellement alloué dont la longueur est la longueur de cette chaîne
la source
Le second provoque la création d'un nouveau tableau de caractères et tous les caractères de la chaîne sont copiés dans ce nouveau tableau de caractères, donc je suppose que le premier est plus rapide (et moins gourmand en mémoire).
la source