Complejidad temporal de la subcadena de Java ()

Respuestas:

137

Nueva respuesta

A partir de la actualización 6 dentro de la vida útil de Java 7, el comportamiento de substringcambió para crear una copia, por lo que cada se Stringrefiere a una char[]que no se comparte con ningún otro objeto, hasta donde yo sé. Entonces, en ese punto, se substring()convirtió en una operación O (n) donde n son los números en la subcadena.

Respuesta anterior: pre-Java 7

Indocumentado, pero en la práctica O (1) si asume que no se requiere recolección de basura, etc.

Simplemente crea un nuevo Stringobjeto que hace referencia al mismo subyacente char[]pero con diferentes valores de compensación y recuento. Por tanto, el coste es el tiempo necesario para realizar la validación y construir un único objeto nuevo (razonablemente pequeño). Eso es O (1) en la medida en que sea sensato hablar sobre la complejidad de las operaciones que pueden variar en el tiempo según la recolección de basura, las cachés de CPU, etc. En particular, no depende directamente de la longitud de la cadena original o la subcadena .

Jon Skeet
fuente
14
+1 para "indocumentado", que es una debilidad desafortunada de la API.
Raedwald
10
No es debilidad. Si el comportamiento está documentado y los detalles de implementación no, permite implementaciones más rápidas en el futuro. En general, Java a menudo define el comportamiento y permite que las implementaciones decidan cuál es la mejor manera. En otras palabras, no debería importarle, después de todo, es Java ;-)
peenut
2
Buen punto peenut, incluso si apenas creo que alguna vez lograrán hacer este más rápido que O (1).
abahgat
9
No, algo como esto debería estar documentado. Un desarrollador debe estar al tanto, en caso de que planee tomar una pequeña subcadena de una cadena grande, esperando que la cadena más grande sea recolectada como basura como lo estaría en .NET.
Qwertie
1
@IvayloToskov: el número de caracteres copiados.
Jon Skeet
33

Era O (1) en versiones anteriores de Java; como dijo Jon, acaba de crear una nueva Cadena con el mismo carácter subyacente [], y un desplazamiento y una longitud diferentes.

Sin embargo, esto realmente ha cambiado a partir de la actualización 6 de Java 7.

Se eliminó el uso compartido de caracteres [] y se eliminaron los campos de desplazamiento y longitud. substring () ahora simplemente copia todos los caracteres en una nueva Cadena.

Ergo, la subcadena es O (n) en la actualización 6 de Java 7

Chi
fuente
2
+1 Este es de hecho el caso de las versiones recientes de Sun Java y OpenJDK. GNU Classpath (y otros, supongo) todavía están usando el viejo paradigma. Desafortunadamente, parece haber un poco de inercia intelectual en esto. Todavía veo publicaciones en 2013 que recomiendan varios enfoques basados ​​en la suposición de que las subcadenas usan un compartido char[]...
thkala
10
Entonces, la nueva versión ya no tiene complejidad O (1). ¿Tiene curiosidad por saber si hay alguna forma alternativa de implementar la subcadena en O (1)? String.substring es un método extremadamente útil.
Yitong Zhou
8

Ahora es una complejidad lineal. Esto es después de solucionar un problema de pérdida de memoria para la subcadena.

Entonces, de Java 1.7.0_06 recuerde que String.substring ahora tiene una complejidad lineal en lugar de una constante.

amigos.pallav
fuente
¿Entonces es peor ahora (para cadenas largas)?
Peter Mortensen
@PeterMortensen sí.
Ido Kessler
3

Añadiendo pruebas a la respuesta de Jon. Tenía la misma duda y quería comprobar si la longitud de la cadena tiene algún efecto en la función de subcadena. Escrito el siguiente código para verificar de qué subcadena de parámetros depende realmente.

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;
        }
    }

}

El resultado de la ejecución en Java 8 es:

 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

Probar la función de subcadena depende de la longitud de la subcadena solicitada, no de la longitud de la cadena.

Pavan Konduri
fuente
1

O (1) debido a que no se realiza ninguna copia de la cadena original, simplemente crea un nuevo objeto contenedor con diferente información de compensación.

Rodion
fuente
1

Juzgue usted mismo a partir de lo siguiente, pero los inconvenientes de rendimiento de Java se encuentran en otro lugar, no aquí en la subcadena de una cadena. Código:

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");
    }

Salida:

subcadena 32768 veces
Suma de longitudes de divisiones = 1494414
Respuesta 2.446679 ms

Si es O (1) o no, depende. Si solo hace referencia a la misma cadena en la memoria, imagine una cadena muy larga, crea una subcadena y deja de hacer referencia a una larga. ¿No sería bueno liberar la memoria por mucho tiempo?

maní
fuente
0

Antes de Java 1.7.0_06: O (1).

Después de Java 1.7.0_06: O (n). Esto se cambió debido a una pérdida de memoria. Después de que los campos offsety countse eliminaron de String, la implementación de la subcadena se convirtió en O (n).

Para obtener más detalles, consulte: http://java-performance.info/changes-to-string-java-1-7-0_06/

omilus
fuente