Complessità temporale della sottostringa di Java ()


Risposte:


142

Nuova risposta

A partire dall'aggiornamento 6 durante la vita di Java 7, il comportamento è substringcambiato per creare una copia, quindi ogni si Stringriferisce a un oggetto char[]che non è condiviso con nessun altro oggetto, per quanto ne so. Quindi, a quel punto, è substring()diventata un'operazione O (n) dove n è il numero nella sottostringa.

Vecchia risposta: pre-Java 7

Non documentato, ma in pratica O (1) se si presume che non sia richiesta la garbage collection, ecc.

Costruisce semplicemente un nuovo Stringoggetto che fa riferimento allo stesso sottostante char[]ma con valori di offset e conteggio diversi. Quindi il costo è il tempo impiegato per eseguire la convalida e costruire un singolo oggetto nuovo (ragionevolmente piccolo). Questo è O (1) per quanto è sensato parlare della complessità delle operazioni che possono variare nel tempo in base alla garbage collection, cache della CPU ecc. In particolare, non dipende direttamente dalla lunghezza della stringa originale o della sottostringa .


14
+1 per "non documentato", che è una sfortunata debolezza dell'API.
Raedwald

10
Non è debolezza. Se il comportamento è documentato ei dettagli di implementazione non lo sono, consente implementazioni più rapide in futuro. In generale, Java spesso definisce il comportamento e consente alle implementazioni di decidere qual è il modo migliore. In altre parole - non dovresti preoccuparti, dopotutto, è Java ;-)
peenut

2
Buon punto, anche se non credo che riusciranno mai a renderlo più veloce di O (1).
abahgat

9
No, qualcosa del genere dovrebbe essere documentato. Uno sviluppatore dovrebbe essere consapevole, nel caso in cui intenda prendere una piccola sottostringa di una stringa di grandi dimensioni, aspettandosi che la stringa più grande venga raccolta in modo indesiderato come sarebbe in .NET.
Qwertie

1
@IvayloToskov: il numero di caratteri copiati.
Jon Skeet

34

Era O (1) nelle versioni precedenti di Java - come ha affermato Jon, ha appena creato una nuova stringa con lo stesso carattere sottostante [] e un offset e una lunghezza diversi.

Tuttavia, questo è effettivamente cambiato a partire dall'aggiornamento 6 di Java 7.

La condivisione char [] è stata eliminata e i campi offset e lunghezza sono stati rimossi. substring () ora copia semplicemente tutti i caratteri in una nuova stringa.

Ergo, la sottostringa è O (n) nell'aggiornamento 6 di Java 7


2
+1 Questo è effettivamente il caso delle recenti versioni di Sun Java e OpenJDK. GNU Classpath (e altri, presumo) stanno ancora utilizzando il vecchio paradigma. Purtroppo sembra che ci sia un po 'di inerzia intellettuale rispetto a questo. Vedo ancora post nel 2013 che raccomandano vari approcci basati sul presupposto che le sottostringhe utilizzino un char[]...
thkala

10
Quindi la nuova versione non ha più complessità O (1). Curioso di sapere che esiste un modo alternativo per implementare la sottostringa in O (1)? String.substring è un metodo estremamente utile.
Yitong Zhou

8

Adesso è una complessità lineare. Questo è dopo aver risolto un problema di perdita di memoria per la sottostringa.

Quindi da Java 1.7.0_06 ricorda che String.substring ora ha una complessità lineare invece di una costante.


Quindi ora è peggio (per stringhe lunghe)?
Peter Mortensen

@PeterMortensen sì.
Ido Kessler

3

Aggiunta di prove alla risposta di Jon. Avevo lo stesso dubbio e volevo verificare se la lunghezza della stringa ha effetti sulla funzione della sottostringa. Scritto il codice seguente per verificare da quale sottostringa di parametro dipende effettivamente.

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

}

L'output durante l'esecuzione in Java 8 è:

 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 funzione di dimostrazione della sottostringa dipende dalla lunghezza della sottostringa richiesta non dalla lunghezza della stringa.


1

O (1) poiché non viene eseguita alcuna copia della stringa originale, crea solo un nuovo oggetto wrapper con informazioni di offset diverse.


1

Giudicate voi stessi dal seguire, ma gli svantaggi delle prestazioni di Java risiedono altrove, non qui nella sottostringa di una stringa. Codice:

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

Produzione:

sottostringa 32768 volte
Somma delle lunghezze delle suddivisioni = 1494414
Risposta 2.446679 ms

Se è O (1) o no, dipende. Se fai riferimento alla stessa stringa in memoria, immagina una stringa molto lunga, crei una sottostringa e smetti di fare riferimento a una stringa lunga. Non sarebbe bello liberare memoria a lungo?


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.