Складність у часі підрядка Java ()


Відповіді:


137

Нова відповідь

Починаючи з оновлення 6 протягом життя Java 7, поведінку substringзмінилося, щоб створити копію - тому кожен Stringпосилається на a, char[]який не є спільним з будь-яким іншим об’єктом, наскільки мені відомо. Тож на той момент substring()перетворилася на операцію O (n), де n - числа в підрядку.

Стара відповідь: pre-Java 7

Без документів - але на практиці O (1), якщо ви вважаєте, що не потрібно збирати сміття тощо.

Він просто створює новий Stringоб'єкт із посиланням на той самий базовий, char[]але з різними значеннями зміщення та відліку. Отже, вартість - це час, необхідний для перевірки та побудови одного нового (досить малого) об’єкта. Це O (1), наскільки розумно говорити про складність операцій, які можуть змінюватися в часі залежно від збору сміття, кеш-пам'яті процесора тощо. Зокрема, це безпосередньо не залежить від довжини вихідного рядка чи підрядка .


14
+1 за "недокументований", що є прикрою слабкістю API.
Raedwald

10
Це не слабкість. Якщо поведінка задокументована, а деталей реалізації немає, це дозволяє пришвидшити впровадження в майбутньому. Загалом, Java часто визначає поведінку і дозволяє реалізаціям вирішити, який найкращий спосіб. Іншими словами - вам все одно, зрештою, це Java ;-)
peenut

2
Хороший момент, арахіс, навіть якщо я навряд чи вірю, що їм коли-небудь вдасться зробити це швидше, ніж O (1).
абахгат

9
Ні, щось подібне слід задокументувати. Розробник повинен знати, на той випадок, якщо він планує взяти невеликий підрядок великого рядка, очікуючи, що більший рядок буде зібраним сміттям, як це було б у .NET.
Кверті

1
@IvayloToskov: Кількість скопійованих символів.
Джон Скіт,

33

Це був O (1) у старих версіях Java - як заявив Джон, він щойно створив новий рядок з тим самим базовим символом [], а також іншим зміщенням та довжиною.

Однак це насправді змінилося, починаючи з оновлення 6 Java 7.

Обмін символами [] було вилучено, а поля зміщення та довжини видалено. substring () тепер просто копіює всі символи в новий рядок.

Ерго, підрядком є ​​O (n) у Java 7 оновлення 6


2
+1 Це справді так у останніх версіях Sun Java та OpenJDK. GNU Classpath (та інші, я припускаю) все ще використовують стару парадигму. На жаль, здається, тут є трохи інтелектуальної інерції. Я все ще бачу повідомлення в 2013 році, які рекомендують різні підходи, засновані на припущенні, що підрядки використовують спільний char[]...
thkala

10
Тож нова версія більше не має складності O (1). Цікаво знати, чи існує якийсь альтернативний спосіб реалізації підрядка в O (1)? String.substring - надзвичайно корисний метод.
Yitong Zhou

8

Зараз це лінійна складність. Це після усунення проблеми витоку пам’яті для підрядка.

Отже, з Java 1.7.0_06 пам’ятайте, що String.substring тепер має лінійну складність замість постійної.


Тож зараз гірше (для довгих струн)?
Пітер Мортенсен

@PeterMortensen так.
Ідо Кесслер,

3

Додавання доказу до відповіді Джона. Я сумнівався і хотів перевірити, чи впливає довжина рядка на функцію підрядка. Написаний наступний код, щоб перевірити, від якого параметра насправді залежить підрядок.

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 є:

 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

Доведення функції підрядка залежить від довжини запитуваної підрядка, а не від довжини рядка.


1

O (1), оскільки копіювання вихідного рядка не виконується, він просто створює новий об'єкт-обгортку з різною інформацією про зміщення.


1

Судіть самі, якщо слідкувати, але недоліки продуктивності Java лежать десь в іншому місці, а не тут, у підрядку рядка. Код:

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

Вихід:

підрядок 32768 разів
Сума довжин розколів = 1494414
Затрачено 2.446679 мс

Якщо це O (1) чи ні, залежить. Якщо ви просто посилаєтесь на той самий рядок у пам'яті, то уявіть собі дуже довгий рядок, ви робите підрядок і припиняєте посилання на довгий. Не було б приємно звільнити пам’ять надовго?


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.