Java 的 substring() 的时间复杂度

Posted

技术标签:

【中文标题】Java 的 substring() 的时间复杂度【英文标题】:Time complexity of Java's substring() 【发布时间】:2011-06-08 11:05:12 【问题描述】:

Java 中String#substring() 方法的时间复杂度是多少?

【问题讨论】:

【参考方案1】:

之前 Java 1.7.0_06:O(1)。

之后 Java 1.7.0_06:O(n)。由于内存泄漏,这已更改。从 String 中删除字段 offsetcount 后,子字符串实现变为 O(n)。

更多详情请参考:http://java-performance.info/changes-to-string-java-1-7-0_06/

【讨论】:

【参考方案2】:

为乔恩的回答添加证据。 我有同样的疑问,想检查字符串的长度是否对子字符串函数有任何影响。编写以下代码来检查子字符串实际依赖的参数。

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

证明子字符串函数取决于请求的子字符串的长度而不是字符串的长度。

【讨论】:

我讨厌成为破坏派对的人,但这不是“证据”而是一种暗示【参考方案3】:

现在是线性复杂度。这是在修复子字符串的内存泄漏问题之后。

所以从 Java 1.7.0_06 开始,请记住 String.substring 现在具有线性复杂度而不是常数。

【讨论】:

所以现在情况更糟(对于长字符串)? @PeterMortensen 是的。【参考方案4】:

新答案

从 Java 7 生命周期内的更新 6 开始,substring 的行为已更改为创建一个副本 - 因此每个 String 都指代一个char[],它与任何其他对象共享,据我所知。所以到那时,substring() 变成了一个 O(n) 操作,其中 n 是子字符串中的数字。

旧答案:Java 7 之前

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

它只是构建一个新的String 对象,引用相同的底层char[],但具有不同的偏移量和计数值。因此,成本是执行验证和构造单个新(相当小)对象所花费的时间。这是 O(1) 就可以根据垃圾收集、CPU 缓存等讨论可能随时间变化的操作的复杂性是明智的。特别是,它不直接取决于原始字符串或子字符串的长度.

【讨论】:

+1 表示“未记录”,这是 API 的一个不幸弱点。 这不是弱点。如果记录了行为,而没有记录实现细节,那么它允许将来更快地实现。一般来说,Java 经常定义行为并让实现来决定什么是最好的方式。换句话说 - 你不应该关心,毕竟,它是 Java ;-) 好点peenut,即使我几乎不相信他们会设法使这个速度超过 O(1)。 不,这样的事情应该记录在案。开发人员应该注意,以防万一他计划从一个大字符串中提取一个小子字符串,并期望较大的字符串像在 .NET 中那样被垃圾收集。 值得注意的是,虽然性能更差,this change was made to address a gc leak【参考方案5】:

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

然而,这实际上从 Java 7 更新 6 开始发生了变化。

char[] 共享被消除,偏移量和长度字段被移除。 substring() 现在只是将所有字符复制到一个新的字符串中。

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

【讨论】:

+1 在最近的 Sun Java 和 OpenJDK 版本中确实如此。 GNU Classpath(以及其他,我假设)仍在使用旧范式。不幸的是,似乎有一点智力惯性w.r.t。这。我仍然看到 2013 年的帖子推荐基于子字符串使用共享 char[]... 的假设的各种方法 所以新版本不再有 O(1) 复杂度。想知道是否有任何替代方法可以在 O(1) 中实现子字符串? String.substring 是一个非常有用的方法。【参考方案6】:

请自行判断,但 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),取决于。如果您只是在内存中引用相同的字符串,那么想象 very 长字符串,您创建子字符串并停止引用长字符串。长时间释放内存不是很好吗?

【讨论】:

【参考方案7】:

O(1) 因为没有复制原始字符串,它只是创建了一个具有不同偏移信息的新包装对象。

【讨论】:

以上是关于Java 的 substring() 的时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章

3. Longest Substring Without Repeating Characters

Longest Substring Without Repeating Characters -- LeetCode

C#中Substring具体怎么用?(复杂的例子看不懂)

Longest Palindromic Substring

C#中Substring具体怎么用?(复杂的例子看不懂)

LeetCode 5. Longest Palindromic Substring