Java的substring()的时间复杂度


Answers:


137

新答案

从Java 7的一生中更新6,行为substring改变,以创建一个副本-所以每Stringchar[]与任何其他对象共享的,据我所知。因此,这时substring()变成O(n)运算,其中n是子字符串中的数字。

旧答案:Java 7之前的版本

未记录-但实际上,如果您假设不需要垃圾回收,则为O(1),等等。

它只是简单地构建一个String引用相同基础char[]但具有不同偏移量和计数值的新对象。因此,成本是执行验证和构造单个新(合理小)对象所花费的时间。就操作复杂性而言,它是O(1),它会根据垃圾收集,CPU缓存等随时间而变化。特别是,它并不直接取决于原始字符串或子字符串的长度。


14
+1表示“未公开”,这是API的不幸缺点。
Raedwald

10
这不是弱点。如果记录了行为,但没有记录实现细节,则可以在将来更快地实现。通常,Java经常定义行为,并让实现决定最佳方法。换句话说-毕竟您不应该在乎Java ;-)
peenut 2011年

2
很好的观点,即使我几乎不相信他们会比O(1)更快地做到这一点。
2011年

9
不,应该记录这样的内容。开发人员应该意识到,以防万一他计划采用大字符串的小子字符串,并希望像.NET中那样对大字符串进行垃圾回收。
Qwertie 2012年

1
@IvayloToskov:复制的字符数。
乔恩·斯基特

33

在Java的旧版本中它是O(1)-正如Jon所说,它只是创建了一个新的String,具有相同的底层char [],以及不同的偏移量和长度。

但是,从Java 7 Update 6开始,这实际上已经改变。

取消了char []共享,并删除了offset和length字段。substring()现在仅将所有字符复制到新的String中。

因此,在Java 7更新6中,子字符串为O(n)


2
+1在最新的Sun Java和OpenJDK版本中确实如此。GNU Classpath(以及其他一些我假设的)仍在使用旧的范例。不幸的是,这似乎有点智力上的惰性。我仍然看到2013年的帖子在假设子字符串使用共享char[]...的基础上建议各种方法
thkala 2013年

10
因此,新版本不再具有O(1)复杂性。想知道是否有其他方法可以在O(1)中实现子字符串吗?String.substring是一个非常有用的方法。
Yitong

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)取决于。如果仅在内存中引用相同的String,然后想象长的String,则创建子字符串并停止引用长的String。长时间释放内存不是很好吗?


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.