Quelle est la complexité temporelle de la méthode String#substring()
en Java?
Nouvelle réponse
Depuis la mise à jour 6 dans Java 7, le comportement de substring
a changé pour créer une copie - donc chaque String
fait référence à un char[]
qui n'est pas partagé avec aucun autre objet, pour autant que je sache. Donc à ce moment-là, substring()
est devenu un = O(n) opération où n est le nombre dans la sous-chaîne.
Ancienne réponse: pré-Java 7
Non documenté - mais en pratique O(1) si vous supposez qu'aucun ramasse-miettes n'est requis, etc.
Il construit simplement un nouvel objet String
faisant référence au même char[]
Sous-jacent mais avec des valeurs de décalage et de comptage différentes. Le coût est donc le temps nécessaire pour effectuer la validation et construire un seul nouvel objet (raisonnablement petit). C'est O(1) pour autant qu'il soit judicieux de parler de la complexité des opérations qui peuvent varier dans le temps en fonction de la collecte des ordures, des caches CPU, etc. En particulier, cela ne dépend pas directement de la longueur de la chaîne d'origine ou de la sous-chaîne.
C'était O(1) dans les anciennes versions de Java - comme Jon l'a dit, il vient de créer une nouvelle chaîne avec le même caractère sous-jacent [], et un décalage et longueur différents.
Cependant, cela a en fait changé commencé avec Java 7 mise à jour 6.
Le partage char [] a été supprimé et les champs offset et length ont été supprimés. substring () copie désormais tous les caractères dans une nouvelle chaîne.
Ergo, la sous-chaîne est O(n) in Java 7 update 6
C'est maintenant une complexité linéaire. C'est après avoir résolu un problème de fuite de mémoire pour la sous-chaîne.
Donc, à partir de Java 1.7.0_06 rappelez-vous que String.substring a maintenant une complexité linéaire au lieu d'une constante.
O (1) car aucune copie de la chaîne d'origine n'est effectuée, elle crée simplement un nouvel objet wrapper avec différentes informations de décalage.
Ajout de preuves à la réponse de Jon. J'avais le même doute et je voulais vérifier si la longueur de la chaîne avait des effets sur la fonction de sous-chaîne. Code suivant écrit pour vérifier de quelle sous-chaîne de paramètres dépend réellement.
import org.Apache.commons.lang.RandomStringUtils;
public class Dummy {
private static final String pool[] = new String[3];
private static int substringLength;
public static void main(String args[]) {
pool[0] = RandomStringUtils.random(2000);
pool[1] = RandomStringUtils.random(10000);
pool[2] = RandomStringUtils.random(100000);
test(10);
test(100);
test(1000);
}
public static void test(int val) {
substringLength = val;
StatsCopy statsCopy[] = new StatsCopy[3];
for (int j = 0; j < 3; j++) {
statsCopy[j] = new StatsCopy();
}
long latency[] = new long[3];
for (int i = 0; i < 10000; i++) {
for (int j = 0; j < 3; j++) {
latency[j] = latency(pool[j]);
statsCopy[j].send(latency[j]);
}
}
for (int i = 0; i < 3; i++) {
System.out.println(
" Avg: "
+ (int) statsCopy[i].getAvg()
+ "\t String length: "
+ pool[i].length()
+ "\tSubstring Length: "
+ substringLength);
}
System.out.println();
}
private static long latency(String a) {
long startTime = System.nanoTime();
a.substring(0, substringLength);
long endtime = System.nanoTime();
return endtime - startTime;
}
private static class StatsCopy {
private long count = 0;
private long min = Integer.MAX_VALUE;
private long max = 0;
private double avg = 0;
public void send(long latency) {
computeStats(latency);
count++;
}
private void computeStats(long latency) {
if (min > latency) min = latency;
if (max < latency) max = latency;
avg = ((float) count / (count + 1)) * avg + (float) latency / (count + 1);
}
public double getAvg() {
return avg;
}
public long getMin() {
return min;
}
public long getMax() {
return max;
}
public long getCount() {
return count;
}
}
}
Sortie lors de l'exécution en Java 8 est:
Avg: 128 String length: 2000 Substring Length: 10
Avg: 127 String length: 10000 Substring Length: 10
Avg: 124 String length: 100000 Substring Length: 10
Avg: 172 String length: 2000 Substring Length: 100
Avg: 175 String length: 10000 Substring Length: 100
Avg: 177 String length: 100000 Substring Length: 100
Avg: 1199 String length: 2000 Substring Length: 1000
Avg: 1186 String length: 10000 Substring Length: 1000
Avg: 1339 String length: 100000 Substring Length: 1000
La fonction de vérification de la sous-chaîne dépend de la longueur de la sous-chaîne demandée et non de la longueur de la chaîne.
Jugez-en par vous-même, mais les inconvénients des performances de Java se trouvent ailleurs, pas ici dans la sous-chaîne d'une chaîne. Code:
public static void main(String[] args) throws IOException {
String longStr = "asjf97zcv.1jm2497z20`1829182oqiwure92874nvcxz,nvz.,xo" +
"aihf[oiefjkas';./.,z][p\\°°°°°°°°?!(*#&(@*&#!)^(*&(*&)(*&" +
"fasdznmcxzvvcxz,vc,mvczvcz,mvcz,mcvcxvc,mvcxcvcxvcxvcxvcx";
int[] indices = new int[32 * 1024];
int[] lengths = new int[indices.length];
Random r = new Random();
final int minLength = 6;
for (int i = 0; i < indices.length; ++i)
{
indices[i] = r.nextInt(longStr.length() - minLength);
lengths[i] = minLength + r.nextInt(longStr.length() - indices[i] - minLength);
}
long start = System.nanoTime();
int avoidOptimization = 0;
for (int i = 0; i < indices.length; ++i)
//avoidOptimization += lengths[i]; //tested - this was cheap
avoidOptimization += longStr.substring(indices[i],
indices[i] + lengths[i]).length();
long end = System.nanoTime();
System.out.println("substring " + indices.length + " times");
System.out.println("Sum of lengths of splits = " + avoidOptimization);
System.out.println("Elapsed " + (end - start) / 1.0e6 + " ms");
}
Sortie:
sous-chaîne 32768 fois Somme des longueurs de fractionnement = 1494414 écoulé 2,4446679 ms
Si c'est O(1) ou pas, cela dépend. Si vous faites simplement référence à la même chaîne en mémoire, alors imaginez très longue chaîne, vous créez une sous-chaîne et arrêtez de faire référence longtemps Un. Ne serait-il pas agréable de libérer de la mémoire pour longtemps?