Java'nın alt dizesinin () zaman karmaşıklığı


Yanıtlar:


142

Yeni cevap

Java 7'nin ömrü içinde güncelleme 6 itibariyle davranışı substringdeğişti bir kopyasını oluşturmak için - her öylesine Stringbir atıfta char[]hangi değil başka bir nesne ile paylaşılan, bildiğim kadarıyla farkındayım olarak. Bu noktada, substring()n'nin alt dizedeki sayılar olduğu bir O (n) işlemi oldu.

Eski cevap: Java 7 öncesi

Belgelenmemiş - ancak pratikte O (1) çöp toplamanın gerekli olmadığını varsayarsanız, vb.

Basitçe Stringaynı temele atıfta bulunan char[]ancak farklı ofset ve sayım değerlerine sahip yeni bir nesne oluşturur . Dolayısıyla maliyet, doğrulamayı gerçekleştirmek ve tek bir yeni (makul ölçüde küçük) nesne oluşturmak için geçen süredir. Çöp toplama, CPU önbellekleri vb. Temelinde zaman içinde değişebilen işlemlerin karmaşıklığından bahsetmek mantıklı olduğu ölçüde O (1) değeridir. Özellikle, doğrudan orijinal dizenin veya alt dizenin uzunluğuna bağlı değildir .


14
API'nin talihsiz bir zayıflığı olan "belgelenmemiş" için +1.
Raedwald

10
Zayıflık değil. Davranış belgelenmişse ve uygulama ayrıntıları değilse, gelecekte daha hızlı uygulamalara izin verir. Genel olarak, Java genellikle davranışı tanımlar ve uygulamaların neyin en iyi yol olduğuna karar vermesine izin verir. Başka bir deyişle - umursamamalısın, sonuçta Java ;-)
peenut

2
İyi bir nokta, bunu O (1) 'den daha hızlı yapmayı başarabileceklerine neredeyse hiç inanmasam bile.
abahgat

9
Hayır, bunun gibi bir şey belgelenmelidir. Bir geliştirici, büyük bir dizenin küçük bir alt dizesini almayı planlaması durumunda, daha büyük dizenin .NET'te olduğu gibi çöp toplanmasını beklediğinin farkında olmalıdır.
Qwertie

1
@IvayloToskov: Kopyalanan karakter sayısı.
Jon Skeet

34

Java'nın eski sürümlerinde O (1) idi - Jon'un belirttiği gibi, aynı temel karakter [] ve farklı bir uzaklık ve uzunluk ile yeni bir String oluşturdu.

Ancak, bu aslında Java 7 güncelleme 6 ile başlayan değişti.

Char [] paylaşımı kaldırıldı ve ofset ve uzunluk alanları kaldırıldı. substring () artık tüm karakterleri yeni bir String'e kopyalar.

Ergo, Java 7 güncelleme 6'da alt dize O (n)


2
+1 Bu gerçekten son Sun Java ve OpenJDK sürümlerinde geçerlidir. GNU Classpath (ve diğerleri, sanırım) hala eski paradigmayı kullanıyor. Maalesef, bununla ilgili biraz entelektüel bir atalet var gibi görünüyor. char[]
Alt

10
Yani yeni sürüm artık O (1) karmaşıklığına sahip değil. O (1) 'de alt dizeyi uygulamanın alternatif bir yolu var mı? String.substring son derece kullanışlı bir yöntemdir.
Yitong Zhou

8

Artık doğrusal karmaşıklık. Bu, alt dizeyle ilgili bir bellek sızıntısı sorununu düzelttikten sonradır.

Yani Java 1.7.0_06'dan String.substring'in artık sabit bir karmaşıklık yerine doğrusal bir karmaşıklığa sahip olduğunu hatırlayın.


Yani şimdi daha mı kötü (uzun dizeler için)?
Peter Mortensen

@PeterMortensen evet.
Ido Kessler

3

Jon'un cevabına kanıt eklemek. Aynı şüphem vardı ve dizge uzunluğunun alt dize işlevi üzerinde herhangi bir etkisi olup olmadığını kontrol etmek istedim. Hangi parametre alt dizesinin gerçekte bağlı olduğunu kontrol etmek için aşağıdaki kod yazılmıştır.

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

}

Java 8'de yürütme çıktı:

 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

Alt dize işlevinin kanıtlanması, dizenin uzunluğuna değil, istenen alt dizenin uzunluğuna bağlıdır.


1

O (1), orijinal dizginin kopyalanması yapılmadığı için, sadece farklı ofset bilgileriyle yeni bir sarmalayıcı nesnesi oluşturur.


1

Kendinizi takip ederek yargılayın, ancak Java'nın performans dezavantajları başka bir yerde yatıyor, burada bir dizenin alt dizesinde değil. Kod:

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

Çıktı:

32768 kez alt dize
Bölme uzunluklarının toplamı = 1494414
Geçen 2,446679 ms

O (1) olup olmaması bağlıdır. Bellekte sadece aynı String'e başvurursanız, çok uzun String'i hayal edin , alt dize oluşturur ve uzun olanı referans vermeyi bırakırsınız. Uzun bir hatırayı serbest bırakmak güzel olmaz mıydı?


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.