ความซับซ้อนของเวลาของสตริงย่อยของ Java ()


คำตอบ:


142

คำตอบใหม่

เมื่ออัปเดต 6 ภายในอายุการใช้งานของ Java 7 พฤติกรรมของการsubstringเปลี่ยนแปลงในการสร้างสำเนา - ดังนั้นทุกStringอ้างถึงchar[]ที่ไม่ได้ใช้ร่วมกับวัตถุอื่น ๆ เท่าที่ฉันทราบ เมื่อถึงจุดนั้นsubstring()กลายเป็นการดำเนินการ O (n) โดยที่ n คือตัวเลขในสตริงย่อย

คำตอบเก่า: pre-Java 7

ไม่มีเอกสาร - แต่ในทางปฏิบัติ O (1) หากคุณถือว่าไม่จำเป็นต้องมีการเก็บขยะ ฯลฯ

เพียงแค่สร้างStringวัตถุใหม่ที่อ้างถึงพื้นฐานเดียวกันchar[]แต่มีค่าชดเชยและจำนวนที่แตกต่างกัน ดังนั้นต้นทุนคือเวลาที่ใช้ในการตรวจสอบความถูกต้องและสร้างออบเจ็กต์ใหม่ (ขนาดเล็กพอสมควร) เพียงชิ้นเดียว นั่นคือ O (1) เท่าที่สมเหตุสมผลที่จะพูดถึงความซับซ้อนของการดำเนินการซึ่งอาจแตกต่างกันไปตามเวลาตามการรวบรวมขยะแคช CPU เป็นต้นโดยเฉพาะอย่างยิ่งมันไม่ได้ขึ้นอยู่กับความยาวของสตริงดั้งเดิมหรือสตริงย่อยโดยตรง .


14
+1 สำหรับ "ไม่มีเอกสาร" ซึ่งเป็นจุดอ่อนที่น่าเสียดายของ API
Raedwald

10
มันไม่ใช่ความอ่อนแอ หากมีการบันทึกพฤติกรรมและไม่มีรายละเอียดการนำไปใช้งานจะช่วยให้สามารถนำไปใช้งานได้เร็วขึ้นในอนาคต โดยทั่วไป Java มักกำหนดพฤติกรรมและให้การนำไปใช้งานตัดสินใจว่าวิธีใดดีที่สุด กล่าวอีกนัยหนึ่ง - คุณไม่ควรสนใจเพราะมันเป็น Java ;-)
peenut

2
จุดยอดเยี่ยมแม้ว่าฉันแทบจะไม่เชื่อว่าพวกเขาจะจัดการให้เร็วกว่า O (1) ได้
abahgat

9
ไม่ควรมีการบันทึกข้อมูลเช่นนี้ นักพัฒนาควรทราบในกรณีที่เขาวางแผนที่จะใช้สตริงย่อยขนาดเล็กของสตริงขนาดใหญ่โดยคาดหวังว่าสตริงที่ใหญ่กว่าจะเป็นขยะที่รวบรวมได้เหมือนกับใน. NET
Qwertie

1
@IvayloToskov: จำนวนอักขระที่คัดลอก
Jon Skeet

34

มันเป็น O (1) ใน Java เวอร์ชันเก่า - ตามที่จอนกล่าวไว้มันเพิ่งสร้างสตริงใหม่ที่มีอักขระพื้นฐานเดียวกัน [] และออฟเซ็ตและความยาวต่างกัน

อย่างไรก็ตามสิ่งนี้ได้เปลี่ยนแปลงไปแล้วโดยเริ่มจากการอัปเดต Java 7 6

การแบ่งปัน char [] ถูกตัดออกและช่องออฟเซ็ตและความยาวจะถูกลบออก สตริงย่อย () ตอนนี้เพียงแค่คัดลอกอักขระทั้งหมดลงในสตริงใหม่

Ergo สตริงย่อยคือ O (n) ใน Java 7 update 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 มีความซับซ้อนเชิงเส้นแทนที่จะเป็นค่าคงที่


ตอนนี้แย่กว่าแล้ว (สำหรับสายยาว)?
Peter Mortensen

@PeterMortensen ใช่
Ido Kessler

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) เนื่องจากไม่มีการคัดลอกสตริงต้นฉบับมันจึงสร้างออบเจ็กต์ Wrapper ใหม่พร้อมข้อมูลออฟเซ็ตที่แตกต่างกัน


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) หรือไม่ขึ้นอยู่กับ หากคุณแค่อ้างอิง String เดียวกันในหน่วยความจำลองนึกภาพString ที่ยาวมากคุณสร้างสตริงย่อยและหยุดการอ้างอิงสตริงที่ยาว จะดีไหมถ้าปล่อยความทรงจำไว้นาน


0

ก่อน Java 1.7.0_06: O (1)

หลังจาก Java 1.7.0_06: O (n) สิ่งนี้มีการเปลี่ยนแปลงเนื่องจากหน่วยความจำรั่ว หลังจากฟิลด์ offsetและcountถูกลบออกจาก String การใช้งานสตริงย่อยจะกลายเป็น O (n)

ดูรายละเอียดเพิ่มเติมได้ที่: http://java-performance.info/changes-to-string-java-1-7-0_06/

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.