Độ phức tạp về thời gian của chuỗi con của Java ()


Câu trả lời:


137

Câu trả lời mới

Tính cập nhật 6 trong đời Java 7, hành vi của substringthay đổi để tạo ra một bản sao - vì vậy mỗi Stringdùng để chỉ một char[]mà là không chia sẻ với bất kỳ đối tượng khác, như xa như tôi biết. Vì vậy, tại thời điểm đó, substring()đã trở thành một phép toán O (n) với n là các số trong chuỗi con.

Câu trả lời cũ: trước Java 7

Không có giấy tờ - nhưng trong thực tế O (1) nếu bạn cho rằng không cần thu gom rác, v.v.

Nó chỉ đơn giản là xây dựng một Stringđối tượng mới tham chiếu đến cùng một cơ sở char[]nhưng với các giá trị bù và đếm khác nhau. Vì vậy, chi phí là thời gian thực hiện xác nhận và xây dựng một đối tượng mới (nhỏ hợp lý). Đó là O (1) khi nói về mức độ phức tạp của các hoạt động có thể thay đổi theo thời gian dựa trên việc thu gom rác, bộ nhớ đệm CPU, v.v. Đặc biệt, nó không phụ thuộc trực tiếp vào độ dài của chuỗi gốc hoặc chuỗi con .


14
+1 cho "không có giấy tờ", đây là một điểm yếu đáng tiếc của API.
Raedwald

10
Đó không phải là điểm yếu. Nếu hành vi được ghi lại và chi tiết triển khai thì không, nó sẽ cho phép triển khai nhanh hơn trong tương lai. Nói chung, Java thường xác định hành vi và cho phép các triển khai quyết định cách tốt nhất. Nói cách khác - bạn không nên quan tâm, sau khi tất cả, nó là Java ;-)
peenut

2
Điểm tốt là peenut, ngay cả khi tôi khó tin rằng họ sẽ làm được cái này nhanh hơn O (1).
abahgat

9
Không, một cái gì đó như thế này nên được ghi lại. Một nhà phát triển nên lưu ý, đề phòng trường hợp anh ta định lấy một chuỗi con nhỏ của một chuỗi lớn, mong đợi chuỗi lớn hơn sẽ được thu gom giống như trong .NET.
Qwertie

1
@IvayloToskov: Số ký tự được sao chép.
Jon Skeet

33

Nó là O (1) trong các phiên bản Java cũ hơn - như Jon đã nói, nó chỉ tạo ra một Chuỗi mới với cùng một ký tự cơ bản [], và một độ lệch và độ dài khác.

Tuy nhiên, điều này đã thực sự thay đổi bắt đầu từ bản cập nhật Java 7 6.

Chia sẻ char [] đã bị loại bỏ và các trường độ dài và độ lệch cũng bị loại bỏ. substring () bây giờ chỉ cần sao chép tất cả các ký tự vào một chuỗi mới.

Ergo, chuỗi con là O (n) trong Java 7 cập nhật 6


2
+1 Đây thực sự là trường hợp trong các phiên bản Sun Java và OpenJDK gần đây. GNU Classpath (và những người khác, tôi giả sử) vẫn đang sử dụng mô hình cũ. Thật không may, dường như có một chút sức ì của trí tuệ với điều này. Tôi vẫn thấy bài viết vào năm 2013 giới thiệu các cách tiếp cận khác nhau dựa trên giả định rằng chuỗi con sử dụng một chia sẻ char[]...
thkala

10
Vì vậy phiên bản mới không còn độ phức tạp O (1) nữa. Tò mò muốn biết có cách nào thay thế để triển khai chuỗi con trong O (1) không? String.substring là một phương pháp cực kỳ hữu ích.
Yitong Zhou

8

Bây giờ nó phức tạp tuyến tính. Đây là sau khi khắc phục sự cố rò rỉ bộ nhớ cho chuỗi con.

Vì vậy, từ Java 1.7.0_06 hãy nhớ rằng String.substring hiện có độ phức tạp tuyến tính thay vì một hằng số.


Vì vậy, nó là tồi tệ hơn bây giờ (đối với chuỗi dài)?
Peter Mortensen

@PeterMortensen vâng.
Ido Kessler

3

Thêm bằng chứng cho câu trả lời của Jon. Tôi cũng có cùng nghi ngờ và muốn kiểm tra xem độ dài của chuỗi có bất kỳ ảnh hưởng nào đến hàm chuỗi con hay không. Đoạn mã sau đây được viết để kiểm tra xem chuỗi con tham số nào thực sự phụ thuộc vào.

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

}

Đầu ra khi thực thi trong Java 8 là:

 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

Chức năng chứng minh chuỗi con phụ thuộc vào độ dài của chuỗi con được yêu cầu chứ không phụ thuộc vào độ dài của chuỗi.


1

O (1) bởi vì không sao chép chuỗi gốc được thực hiện, nó chỉ tạo một đối tượng bao bọc mới với thông tin bù đắp khác nhau.


1

Đánh giá cho chính bạn từ việc theo dõi, nhưng những hạn chế về hiệu suất của Java nằm ở một nơi khác, không phải ở đây trong chuỗi con của một chuỗi. Mã:

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

Đầu ra:

chuỗi con 32768 lần
Tổng độ dài của các phần chia = 1494414
Đã trôi qua 2,446679 mili giây

Nếu nó là O (1) hay không, phụ thuộc. Nếu bạn chỉ tham chiếu cùng một Chuỗi trong bộ nhớ, thì hãy tưởng tượng một Chuỗi rất dài, bạn tạo chuỗi con và dừng tham chiếu chuỗi dài. Sẽ không tốt khi giải phóng bộ nhớ trong một thời gian dài?


0

Trước Java 1.7.0_06: O (1).

Sau Java 1.7.0_06: O (n). Điều này đã được thay đổi, do bộ nhớ bị rò rỉ. Sau khi các trường offsetcountđược xóa khỏi Chuỗi, việc triển khai chuỗi con trở thành O (n).

Để biết thêm chi tiết, vui lòng tham khảo: http://java-performance.info/changes-to-string-java-1-7-0_06/

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.