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];
}
EDIT:
Ce que j'aimerais savoir, c'est si le coût d'appeler de manière répétée la méthode charAt
au cours d'une longue itération finit par être inférieur ou supérieur au coût d'un appel unique à toCharArray
au début puis 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 ayant à l'esprit le temps de préchauffage de JIT, le temps de démarrage de la JVM, etc., et pas simplement la différence entre deux appels à System.currentTimeMillis()
.
PREMIÈRE MISE À JOUR: Avant d’essayer cela dans un environnement de production (déconseillé), lisez ceci en premier: http://www.javaspecialists.eu/archive/Issue237.html À partir de Java 9, la solution décrite ne fonctionnera plus car Java stockera les chaînes sous forme d'octet [] par défaut.
DEUXIÈME MISE À JOUR: À compter du 2016-10-25, sur mon AMDx64 8core et ma source 1.8, il n'y a pas de différence entre l'utilisation de 'charAt' et l'accès aux champs. Il semble que le fichier jvm soit suffisamment optimisé pour intégrer et rationaliser tous les appels 'string.charAt (n)'.
Tout dépend de la longueur de la String
inspectée. Si, comme le dit la question, il s'agit de chaînes longues , le moyen le plus rapide d'inspecter la chaîne consiste à utiliser la réflexion pour accéder au support char[]
de la chaîne.
Un benchmark entièrement aléatoire avec JDK 8 (win32 et win64) sur un processeur 64 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 de String.charAt(n)
est le plus rapide pour les petites chaînes et que l’utilisation de reflection
pour accéder au tableau de sauvegarde des chaînes est presque deux fois plus rapide pour les grandes chaînes.
9 techniques d'optimisation différentes sont essayées.
Tout le contenu de la chaîne est randomisé
Les tests sont effectués pour des tailles de chaîne multiples de deux commençant par 0,1,2,4,8,16, etc.
Les tests sont effectués 1 000 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é d'avant en arrière pour montrer l'effet du réchauffement de la machine virtuelle Java sur l'optimisation et les délais.
La suite complète est exécutée deux fois, une en mode -client
et l'autre en mode -server
.
Pour les chaînes de 1 à 256 caractères , l'appel de string.charAt(i)
gagne avec un traitement moyen de 13,4 à 588 millions de caractères par seconde.
En outre, il est globalement 5,5% plus rapide (client) et 13,9% (serveur), comme ceci:
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
que comme ceci avec une variable de longueur finale locale:
final int len = data.length();
for (int i = 0; i < len; i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
Pour les chaînes longues, 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 et il peut ensuite être réutilisé dans la bibliothèque sur différentes chaînes. Il est intéressant de noter que, 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 boucle. Voici comment l’accès aux champs peut être configuré le plus rapidement:
final Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
try {
final char[] chars = (char[]) field.get(data);
final int len = chars.length;
for (int i = 0; i < len; i++) {
if (chars[i] <= ' ') {
doThrow();
}
}
return len;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
L'accès aux champs commence par la victoire après des chaînes de 32 caractères en mode serveur sur une machine Java 64 bits sur une machine AMD 64. Cela n'a pas été vu jusqu'à 512 caractères de longueur en mode client.
À noter également, lorsque j’exécutais JDK 8 (build 32 bits) en mode serveur, les performances globales étaient 7% plus lentes pour les grandes et les petites chaînes. C'était avec la construction 121 déc 2013 de JDK 8 version anticipée. Donc, pour le moment, 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 vaille la peine d'être invoqué se trouve sur une machine 64 bits. Sinon, les performances sont entravées.
Pour la construction 32 bits s'exécutant dans -server mode
sur un AMD64, je peux dire ceci:
À noter également, String.chars () (Stream et la version parallèle) est un buste. Bien plus lent que tout autre moyen. L'API Streams
est un moyen plutôt lent d'exécuter des opérations générales sur les chaînes.
Java String peut avoir un prédicat acceptant des méthodes optimisées telles que "conte" (prédicat), forEach (consommateur), forEachWithIndex (consommateur). Ainsi, sans qu'il soit nécessaire que l'utilisateur connaisse la longueur ou répète les appels aux méthodes String, celles-ci pourraient aider à analyser les bibliothèques beep-beep beep
speedup.
Continue de rêver :)
Joyeux cordes!
~ SH
"charAt1" - VÉRIFIEZ LE CONTENU DE LA CHAÎNE DE LA MANIÈRE habituelle:
int charAtMethod1(final String data) {
final int len = data.length();
for (int i = 0; i < len; i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return len;
}
"charAt2" - MÊME QUE CI-DESSUS MAIS UTILISER String.length () au lieu de créer un local final pour la durée
int charAtMethod2(final String data) {
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return data.length();
}
"stream" - UTILISEZ LE NOUVEAU IntStream de Java-8 String et transmettez-le comme un prédictif pour le contrôle
int streamMethod(final String data, final IntPredicate predicate) {
if (data.chars().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
"streamPara" - MEME CI-DESSUS, MAIS OH-LA-LA - ALLEZ PARALLELE !!!
// avoid this at all costs
int streamParallelMethod(final String data, IntPredicate predicate) {
if (data.chars().parallel().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
"réutiliser" - REMPLISSEZ UN CARTE RÉUTILISABLE AVEC LES CORDES
int reuseBuffMethod(final char[] reusable, final String data) {
final int len = data.length();
data.getChars(0, len, reusable, 0);
for (int i = 0; i < len; i++) {
if (reusable[i] <= ' ') {
doThrow();
}
}
return len;
}
"new1" - OBTENEZ UNE NOUVELLE COPIE DU CHAR [] DE LA CHAÎNE
int newMethod1(final String data) {
final int len = data.length();
final char[] copy = data.toCharArray();
for (int i = 0; i < len; i++) {
if (copy[i] <= ' ') {
doThrow();
}
}
return len;
}
"new2" - MÊME QUE CI-DESSUS, MAIS UTILISER "POUR CHAQUE"
int newMethod2(final String data) {
for (final char c : data.toCharArray()) {
if (c <= ' ') {
doThrow();
}
}
return data.length();
}
"field1" - FANCY !! OBTENIR UN TERRAIN POUR ACCÉDER AU CARTE INTERNE DE LA CHAÎNE []
int fieldMethod1(final Field field, final String data) {
try {
final char[] chars = (char[]) field.get(data);
final int len = chars.length;
for (int i = 0; i < len; i++) {
if (chars[i] <= ' ') {
doThrow();
}
}
return len;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
"field2" - MÊME QUE CI-DESSUS, MAIS UTILISER "FOR-EACH"
int fieldMethod2(final Field field, final String data) {
final char[] chars;
try {
chars = (char[]) field.get(data);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
for (final char c : chars) {
if (c <= ' ') {
doThrow();
}
}
return chars.length;
}
-client
MODE (tests avant et arrière combinés)Remarque: le mode-client avec Java 32 bits et le mode-serveur avec Java 64 bits sont identiques à ceux décrits ci-dessous sur mon ordinateur AMD64.
Size WINNER charAt1 charAt2 stream streamPar reuse new1 new2 field1 field2
1 charAt 77.0 72.0 462.0 584.0 127.5 89.5 86.0 159.5 165.0
2 charAt 38.0 36.5 284.0 32712.5 57.5 48.3 50.3 89.0 91.5
4 charAt 19.5 18.5 458.6 3169.0 33.0 26.8 27.5 54.1 52.6
8 charAt 9.8 9.9 100.5 1370.9 17.3 14.4 15.0 26.9 26.4
16 charAt 6.1 6.5 73.4 857.0 8.4 8.2 8.3 13.6 13.5
32 charAt 3.9 3.7 54.8 428.9 5.0 4.9 4.7 7.0 7.2
64 charAt 2.7 2.6 48.2 232.9 3.0 3.2 3.3 3.9 4.0
128 charAt 2.1 1.9 43.7 138.8 2.1 2.6 2.6 2.4 2.6
256 charAt 1.9 1.6 42.4 90.6 1.7 2.1 2.1 1.7 1.8
512 field1 1.7 1.4 40.6 60.5 1.4 1.9 1.9 1.3 1.4
1,024 field1 1.6 1.4 40.0 45.6 1.2 1.9 2.1 1.0 1.2
2,048 field1 1.6 1.3 40.0 36.2 1.2 1.8 1.7 0.9 1.1
4,096 field1 1.6 1.3 39.7 32.6 1.2 1.8 1.7 0.9 1.0
8,192 field1 1.6 1.3 39.6 30.5 1.2 1.8 1.7 0.9 1.0
16,384 field1 1.6 1.3 39.8 28.4 1.2 1.8 1.7 0.8 1.0
32,768 field1 1.6 1.3 40.0 26.7 1.3 1.8 1.7 0.8 1.0
65,536 field1 1.6 1.3 39.8 26.3 1.3 1.8 1.7 0.8 1.0
131,072 field1 1.6 1.3 40.1 25.4 1.4 1.9 1.8 0.8 1.0
262,144 field1 1.6 1.3 39.6 25.2 1.5 1.9 1.9 0.8 1.0
-server
MODE (tests en avant et en arrière combinés)Remarque: il s'agit du test de Java 32 bits s'exécutant en mode serveur sur un processeur AMD64. Le mode serveur pour Java 64 bits était identique à celui de Java 32 bits en mode client, à l'exception de l'accès au champ qui gagnait après 32 caractères.
Size WINNER charAt1 charAt2 stream streamPar reuse new1 new2 field1 field2
1 charAt 74.5 95.5 524.5 783.0 90.5 102.5 90.5 135.0 151.5
2 charAt 48.5 53.0 305.0 30851.3 59.3 57.5 52.0 88.5 91.8
4 charAt 28.8 32.1 132.8 2465.1 37.6 33.9 32.3 49.0 47.0
8 new2 18.0 18.6 63.4 1541.3 18.5 17.9 17.6 25.4 25.8
16 new2 14.0 14.7 129.4 1034.7 12.5 16.2 12.0 16.0 16.6
32 new2 7.8 9.1 19.3 431.5 8.1 7.0 6.7 7.9 8.7
64 reuse 6.1 7.5 11.7 204.7 3.5 3.9 4.3 4.2 4.1
128 reuse 6.8 6.8 9.0 101.0 2.6 3.0 3.0 2.6 2.7
256 field2 6.2 6.5 6.9 57.2 2.4 2.7 2.9 2.3 2.3
512 reuse 4.3 4.9 5.8 28.2 2.0 2.6 2.6 2.1 2.1
1,024 charAt 2.0 1.8 5.3 17.6 2.1 2.5 3.5 2.0 2.0
2,048 charAt 1.9 1.7 5.2 11.9 2.2 3.0 2.6 2.0 2.0
4,096 charAt 1.9 1.7 5.1 8.7 2.1 2.6 2.6 1.9 1.9
8,192 charAt 1.9 1.7 5.1 7.6 2.2 2.5 2.6 1.9 1.9
16,384 charAt 1.9 1.7 5.1 6.9 2.2 2.5 2.5 1.9 1.9
32,768 charAt 1.9 1.7 5.1 6.1 2.2 2.5 2.5 1.9 1.9
65,536 charAt 1.9 1.7 5.1 5.5 2.2 2.4 2.4 1.9 1.9
131,072 charAt 1.9 1.7 5.1 5.4 2.3 2.5 2.5 1.9 1.9
262,144 charAt 1.9 1.7 5.1 5.1 2.3 2.5 2.5 1.9 1.9
(pour tester sur Java 7 et versions antérieures, supprimez les tests des deux flux)
import Java.lang.reflect.Field;
import Java.util.ArrayList;
import Java.util.Collections;
import Java.util.List;
import Java.util.Random;
import Java.util.function.IntPredicate;
/**
* @author Saint Hill <http://stackoverflow.com/users/1584255/saint-hill>
*/
public final class TestStrings {
// we will not test strings longer than 512KM
final int MAX_STRING_SIZE = 1024 * 256;
// for each string size, we will do all the tests
// this many times
final int TRIES_PER_STRING_SIZE = 1000;
public static void main(String[] args) throws Exception {
new TestStrings().run();
}
void run() throws Exception {
// double the length of the data until it reaches MAX chars long
// 0,1,2,4,8,16,32,64,128,256 ...
final List<Integer> sizes = new ArrayList<>();
for (int n = 0; n <= MAX_STRING_SIZE; n = (n == 0 ? 1 : n * 2)) {
sizes.add(n);
}
// CREATE RANDOM (FOR SHUFFLING ORDER OF TESTS)
final Random random = new Random();
System.out.println("Rate in nanoseconds per character inspected.");
System.out.printf("==== FORWARDS (tries per size: %s) ==== \n", TRIES_PER_STRING_SIZE);
printHeadings(TRIES_PER_STRING_SIZE, random);
for (int size : sizes) {
reportResults(size, test(size, TRIES_PER_STRING_SIZE, random));
}
// reverse order or string sizes
Collections.reverse(sizes);
System.out.println("");
System.out.println("Rate in nanoseconds per character inspected.");
System.out.printf("==== BACKWARDS (tries per size: %s) ==== \n", TRIES_PER_STRING_SIZE);
printHeadings(TRIES_PER_STRING_SIZE, random);
for (int size : sizes) {
reportResults(size, test(size, TRIES_PER_STRING_SIZE, random));
}
}
///
///
/// METHODS OF CHECKING THE CONTENTS
/// OF A STRING. ALWAYS CHECKING FOR
/// WHITESPACE (CHAR <=' ')
///
///
// CHECK THE STRING CONTENTS
int charAtMethod1(final String data) {
final int len = data.length();
for (int i = 0; i < len; i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return len;
}
// SAME AS ABOVE BUT USE String.length()
// instead of making a new final local int
int charAtMethod2(final String data) {
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return data.length();
}
// USE new Java-8 String's IntStream
// pass it a PREDICATE to do the checking
int streamMethod(final String data, final IntPredicate predicate) {
if (data.chars().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
// OH LA LA - GO PARALLEL!!!
int streamParallelMethod(final String data, IntPredicate predicate) {
if (data.chars().parallel().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
// Re-fill a resuable char[] with the contents
// of the String's char[]
int reuseBuffMethod(final char[] reusable, final String data) {
final int len = data.length();
data.getChars(0, len, reusable, 0);
for (int i = 0; i < len; i++) {
if (reusable[i] <= ' ') {
doThrow();
}
}
return len;
}
// Obtain a new copy of char[] from String
int newMethod1(final String data) {
final int len = data.length();
final char[] copy = data.toCharArray();
for (int i = 0; i < len; i++) {
if (copy[i] <= ' ') {
doThrow();
}
}
return len;
}
// Obtain a new copy of char[] from String
// but use FOR-EACH
int newMethod2(final String data) {
for (final char c : data.toCharArray()) {
if (c <= ' ') {
doThrow();
}
}
return data.length();
}
// FANCY!
// OBTAIN FIELD FOR ACCESS TO THE STRING'S
// INTERNAL CHAR[]
int fieldMethod1(final Field field, final String data) {
try {
final char[] chars = (char[]) field.get(data);
final int len = chars.length;
for (int i = 0; i < len; i++) {
if (chars[i] <= ' ') {
doThrow();
}
}
return len;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
// same as above but use FOR-EACH
int fieldMethod2(final Field field, final String data) {
final char[] chars;
try {
chars = (char[]) field.get(data);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
for (final char c : chars) {
if (c <= ' ') {
doThrow();
}
}
return chars.length;
}
/**
*
* Make a list of tests. We will shuffle a copy of this list repeatedly
* while we repeat this test.
*
* @param data
* @return
*/
List<Jobber> makeTests(String data) throws Exception {
// make a list of tests
final List<Jobber> tests = new ArrayList<Jobber>();
tests.add(new Jobber("charAt1") {
int check() {
return charAtMethod1(data);
}
});
tests.add(new Jobber("charAt2") {
int check() {
return charAtMethod2(data);
}
});
tests.add(new Jobber("stream") {
final IntPredicate predicate = new IntPredicate() {
public boolean test(int value) {
return value <= ' ';
}
};
int check() {
return streamMethod(data, predicate);
}
});
tests.add(new Jobber("streamPar") {
final IntPredicate predicate = new IntPredicate() {
public boolean test(int value) {
return value <= ' ';
}
};
int check() {
return streamParallelMethod(data, predicate);
}
});
// Reusable char[] method
tests.add(new Jobber("reuse") {
final char[] cbuff = new char[MAX_STRING_SIZE];
int check() {
return reuseBuffMethod(cbuff, data);
}
});
// New char[] from String
tests.add(new Jobber("new1") {
int check() {
return newMethod1(data);
}
});
// New char[] from String
tests.add(new Jobber("new2") {
int check() {
return newMethod2(data);
}
});
// Use reflection for field access
tests.add(new Jobber("field1") {
final Field field;
{
field = String.class.getDeclaredField("value");
field.setAccessible(true);
}
int check() {
return fieldMethod1(field, data);
}
});
// Use reflection for field access
tests.add(new Jobber("field2") {
final Field field;
{
field = String.class.getDeclaredField("value");
field.setAccessible(true);
}
int check() {
return fieldMethod2(field, data);
}
});
return tests;
}
/**
* We use this class to keep track of test results
*/
abstract class Jobber {
final String name;
long nanos;
long chars;
long runs;
Jobber(String name) {
this.name = name;
}
abstract int check();
final double nanosPerChar() {
double charsPerRun = chars / runs;
long nanosPerRun = nanos / runs;
return charsPerRun == 0 ? nanosPerRun : nanosPerRun / charsPerRun;
}
final void run() {
runs++;
long time = System.nanoTime();
chars += check();
nanos += System.nanoTime() - time;
}
}
// MAKE A TEST STRING OF RANDOM CHARACTERS A-Z
private String makeTestString(int testSize, char start, char end) {
Random r = new Random();
char[] data = new char[testSize];
for (int i = 0; i < data.length; i++) {
data[i] = (char) (start + r.nextInt(end));
}
return new String(data);
}
// WE DO THIS IF WE FIND AN ILLEGAL CHARACTER IN THE STRING
public void doThrow() {
throw new RuntimeException("Bzzzt -- Illegal Character!!");
}
/**
* 1. get random string of correct length 2. get tests (List<Jobber>) 3.
* perform tests repeatedly, shuffling each time
*/
List<Jobber> test(int size, int tries, Random random) throws Exception {
String data = makeTestString(size, 'A', 'Z');
List<Jobber> tests = makeTests(data);
List<Jobber> copy = new ArrayList<>(tests);
while (tries-- > 0) {
Collections.shuffle(copy, random);
for (Jobber ti : copy) {
ti.run();
}
}
// check to make sure all char counts the same
long runs = tests.get(0).runs;
long count = tests.get(0).chars;
for (Jobber ti : tests) {
if (ti.runs != runs && ti.chars != count) {
throw new Exception("Char counts should match if all correct algorithms");
}
}
return tests;
}
private void printHeadings(final int TRIES_PER_STRING_SIZE, final Random random) throws Exception {
System.out.print(" Size");
for (Jobber ti : test(0, TRIES_PER_STRING_SIZE, random)) {
System.out.printf("%9s", ti.name);
}
System.out.println("");
}
private void reportResults(int size, List<Jobber> tests) {
System.out.printf("%6d", size);
for (Jobber ti : tests) {
System.out.printf("%,9.2f", ti.nanosPerChar());
}
System.out.println("");
}
}
Ceci est juste une micro-optimisation dont vous ne devriez pas vous inquiéter.
char[] chars = str.toCharArray();
vous renvoie une copie des tableaux str
(dans JDK, il renvoie une copie des caractères en appelant System.arrayCopy
).
Sinon, str.charAt()
ne vérifie que si l'index est bien 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 la machine virtuelle Java.
Juste pour curiosité et pour comparer avec la réponse de Saint Hill.
Si vous devez traiter des données volumineuses, vous ne devez pas utiliser la machine virtuelle Java en mode client. Le mode client n'est pas fait pour les optimisations.
Comparons les résultats des tests de performance @Saint Hill à l'aide d'une machine virtuelle Java en mode client et en mode serveur.
Core2Quad Q6600 G0 @ 2.4GHz
JavaSE 1.7.0_40
Voir aussi: Différences réelles entre "serveur Java" et "client-Java"?
MODE CLIENT:
len = 2: 111k charAt(i), 105k cbuff[i], 62k new[i], 17k field access. (chars/ms)
len = 4: 285k charAt(i), 166k cbuff[i], 114k new[i], 43k field access. (chars/ms)
len = 6: 315k charAt(i), 230k cbuff[i], 162k new[i], 69k field access. (chars/ms)
len = 8: 333k charAt(i), 275k cbuff[i], 181k new[i], 85k field access. (chars/ms)
len = 12: 342k charAt(i), 342k cbuff[i], 222k new[i], 117k field access. (chars/ms)
len = 16: 363k charAt(i), 347k cbuff[i], 275k new[i], 152k field access. (chars/ms)
len = 20: 363k charAt(i), 392k cbuff[i], 289k new[i], 180k field access. (chars/ms)
len = 24: 375k charAt(i), 428k cbuff[i], 311k new[i], 205k field access. (chars/ms)
len = 28: 378k charAt(i), 474k cbuff[i], 341k new[i], 233k field access. (chars/ms)
len = 32: 376k charAt(i), 492k cbuff[i], 340k new[i], 251k field access. (chars/ms)
len = 64: 374k charAt(i), 551k cbuff[i], 374k new[i], 367k field access. (chars/ms)
len = 128: 385k charAt(i), 624k cbuff[i], 415k new[i], 509k field access. (chars/ms)
len = 256: 390k charAt(i), 675k cbuff[i], 436k new[i], 619k field access. (chars/ms)
len = 512: 394k charAt(i), 703k cbuff[i], 439k new[i], 695k field access. (chars/ms)
len = 1024: 395k charAt(i), 718k cbuff[i], 462k new[i], 742k field access. (chars/ms)
len = 2048: 396k charAt(i), 725k cbuff[i], 471k new[i], 767k field access. (chars/ms)
len = 4096: 396k charAt(i), 727k cbuff[i], 459k new[i], 780k field access. (chars/ms)
len = 8192: 397k charAt(i), 712k cbuff[i], 446k new[i], 772k field access. (chars/ms)
MODE SERVEUR:
len = 2: 86k charAt(i), 41k cbuff[i], 46k new[i], 80k field access. (chars/ms)
len = 4: 571k charAt(i), 250k cbuff[i], 97k new[i], 222k field access. (chars/ms)
len = 6: 666k charAt(i), 333k cbuff[i], 125k new[i], 315k field access. (chars/ms)
len = 8: 800k charAt(i), 400k cbuff[i], 181k new[i], 380k field access. (chars/ms)
len = 12: 800k charAt(i), 521k cbuff[i], 260k new[i], 545k field access. (chars/ms)
len = 16: 800k charAt(i), 592k cbuff[i], 296k new[i], 640k field access. (chars/ms)
len = 20: 800k charAt(i), 666k cbuff[i], 408k new[i], 800k field access. (chars/ms)
len = 24: 800k charAt(i), 705k cbuff[i], 452k new[i], 800k field access. (chars/ms)
len = 28: 777k charAt(i), 736k cbuff[i], 368k new[i], 933k field access. (chars/ms)
len = 32: 800k charAt(i), 780k cbuff[i], 571k new[i], 969k field access. (chars/ms)
len = 64: 800k charAt(i), 901k cbuff[i], 800k new[i], 1306k field access. (chars/ms)
len = 128: 1084k charAt(i), 888k cbuff[i], 633k new[i], 1620k field access. (chars/ms)
len = 256: 1122k charAt(i), 966k cbuff[i], 729k new[i], 1790k field access. (chars/ms)
len = 512: 1163k charAt(i), 1007k cbuff[i], 676k new[i], 1910k field access. (chars/ms)
len = 1024: 1179k charAt(i), 1027k cbuff[i], 698k new[i], 1954k field access. (chars/ms)
len = 2048: 1184k charAt(i), 1043k cbuff[i], 732k new[i], 2007k field access. (chars/ms)
len = 4096: 1188k charAt(i), 1049k cbuff[i], 742k new[i], 2031k field access. (chars/ms)
len = 8192: 1157k charAt(i), 1032k cbuff[i], 723k new[i], 2048k field access. (chars/ms)
CONCLUSION:
Comme vous pouvez le constater, le mode serveur est beaucoup plus rapide.
Le premier utilisant str.charAt
devrait être plus rapide.
Si vous creusez dans le code source de la classe String
, nous pouvons voir que charAt
est implémenté comme suit:
public char charAt(int index) {
if ((index < 0) || (index >= count)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index + offset];
}
Ici, tout ce qu’il fait est d’indexer un tableau et de renvoyer la valeur.
Maintenant, si nous voyons l'implémentation de toCharArray
, nous trouverons ce qui suit:
public char[] toCharArray() {
char result[] = new char[count];
getChars(0, count, result, 0);
return result;
}
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > count) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, offset + srcBegin, dst, dstBegin,
srcEnd - srcBegin);
}
Comme vous le voyez, il s'agit d'un System.arraycopy
qui va certainement être un peu plus lent que de ne pas le faire.
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 grandes chaînes. Vous pouvez exécuter le code ci-dessous pour le voir vous-même.
char [] ch = new char[1_000_000_00];
String str = new String(ch); // to create a large string
// ---> from here
long currentTime = System.nanoTime();
for (int i = 0, n = str.length(); i < n; i++) {
char c = str.charAt(i);
}
// ---> to here
System.out.println("str.charAt(i):"+(System.nanoTime()-currentTime)/1000000.0 +" (ms)");
/**
* ch = str.toCharArray() itself takes lots of time
*/
// ---> from here
currentTime = System.nanoTime();
ch = str.toCharArray();
for (int i = 0, n = str.length(); i < n; i++) {
char c = ch[i];
}
// ---> to here
System.out.println("ch = str.toCharArray() + c = ch[i] :"+(System.nanoTime()-currentTime)/1000000.0 +" (ms)");
sortie:
str.charAt(i):5.492102 (ms)
ch = str.toCharArray() + c = ch[i] :79.400064 (ms)
String.toCharArray()
crée un nouveau tableau de caractères, signifie une allocation de mémoire de longueur de chaîne, puis copie le tableau de caractères d'origine de chaîne à l'aide de System.arraycopy()
, puis renvoie cette copie à l'appelant. String.charAt () renvoie le caractère à la position i
à partir de la copie d'origine. C'est pourquoi String.charAt()
sera plus rapide que String.toCharArray()
. Bien que String.toCharArray()
renvoie la copie et non le caractère du tableau de chaînes d'origine, où String.charAt()
renvoie le caractère du tableau de caractères d'origine. Le code ci-dessous renvoie une valeur à l'index spécifié de cette chaîne.
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
le code ci-dessous retourne un tableau de caractères nouvellement alloué dont la longueur est la longueur de cette chaîne
public char[] toCharArray() {
// Cannot use Arrays.copyOf because of class initialization order issues
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
On dirait que niether est plus rapide ou plus lent
public static void main(String arguments[]) {
//Build a long string
StringBuilder sb = new StringBuilder();
for(int j = 0; j < 10000; j++) {
sb.append("a really, really long string");
}
String str = sb.toString();
for (int testscount = 0; testscount < 10; testscount ++) {
//Test 1
long start = System.currentTimeMillis();
for(int c = 0; c < 10000000; c++) {
for (int i = 0, n = str.length(); i < n; i++) {
char chr = str.charAt(i);
doSomethingWithChar(chr);//To trick JIT optimistaion
}
}
System.out.println("1: " + (System.currentTimeMillis() - start));
//Test 2
start = System.currentTimeMillis();
char[] chars = str.toCharArray();
for(int c = 0; c < 10000000; c++) {
for (int i = 0, n = chars.length; i < n; i++) {
char chr = chars[i];
doSomethingWithChar(chr);//To trick JIT optimistaion
}
}
System.out.println("2: " + (System.currentTimeMillis() - start));
System.out.println();
}
}
public static void doSomethingWithChar(char chr) {
int newInt = chr << 2;
}
Pour les longues chaînes, je choisirai la première. Pourquoi copier autour de longues chaînes? Documentations dit:
public char [] toCharArray () Convertit cette chaîne en un nouveau tableau de caractères.
Retourne: un tableau de caractères nouvellement alloué dont la longueur est la longueur de cette chaîne et dont le contenu est initialisé pour contenir la séquence de caractères représentée par cette chaîne.
// Edit 1
J'ai changé le test pour tromper l'optimisation JIT.
// Edit 2
Répétez le test 10 fois pour laisser JVM se réchauffer.
// Edit
Conclusions:
Tout d'abord str.toCharArray();
copie la chaîne entière en mémoire. Cela peut consommer de la mémoire pour de longues chaînes. La méthode String.charAt( )
recherche le caractère dans le tableau de caractères dans l'index de contrôle de la classe String avant. Il semble que pour une méthode assez courte, Strings first (c'est-à-dire la méthode chatAt
) est un peu plus lente en raison de cette vérification d'index. Mais si la chaîne est suffisamment longue, la copie du tableau de caractères entier devient plus lente et la première méthode est plus rapide. Plus la chaîne est longue, plus la vitesse toCharArray
est lente. Essayez de changer la limite dans la boucle for(int j = 0; j < 10000; j++)
pour la voir. Si nous laissons JVM, le code de réchauffement est plus rapide, mais les proportions sont les mêmes.
Après tout, il ne s'agit que de la micro-optimisation.
La seconde provoque la création d'un nouveau tableau de caractères et la copie de tous les caractères de la chaîne dans ce nouveau tableau de caractères. Je suppose donc que le premier est plus rapide (et moins gourmand en mémoire).