Javaのsubstring()の時間計算量


回答:


137

新しい答え

Java 7の存続期間内の更新6の時点でsubstring、コピーを作成するために動作が変更されました。したがって、私が知る限り、すべてが他のオブジェクトと共有されていないString参照します。したがって、その時点で、はO(n)演算になりました。ここで、nは部分文字列の数値です。char[]substring()

古い答え:Java7以前

文書化されていませんが、実際には、ガベージコレクションが不要であると想定している場合はO(1)などです。

String同じ原資産を参照しているchar[]が、オフセットとカウントの値が異なる新しいオブジェクトを作成するだけです。したがって、コストは、検証を実行し、単一の新しい(適度に小さい)オブジェクトを構築するのにかかる時間です。ガベージコレクションやCPUキャッシュなどに基づいて時間とともに変化する可能性のある操作の複雑さについて話すのが賢明な限り、これはO(1)です。特に、元の文字列または部分文字列の長さに直接依存しません。 。


14
「文書化されていない」場合は+1。これはAPIの不幸な弱点です。
レドワルド2011年

10
それは弱点ではありません。動作が文書化されていて、実装の詳細が文書化されていない場合、将来的にはより高速な実装が可能になります。一般に、Javaは多くの場合、動作を定義し、実装が最善の方法を決定できるようにします。言い換えれば、-結局のところ、それはJavaです;-)
peenut 2011年

2
良い点は、O(1)よりも速くすることができるとは信じられないかもしれませんが。
abahgat 2011

9
いいえ、このようなものを文書化する必要があります。開発者は、大きな文字列の小さな部分文字列を取得する場合に備えて、.NETの場合と同様に、大きな文字列がガベージコレクションされることを期待していることに注意する必要があります。
qwertie 2012年

1
@IvayloToskov:コピーされた文字の数。
ジョンスキート2016

33

古いバージョンのJavaではO(1)でした。Jonが述べたように、基になるchar []が同じで、オフセットと長さが異なる新しい文字列を作成しただけです。

ただし、これは実際にはJava 7 update6から変更されています。

char []の共有が削除され、offsetフィールドとlengthフィールドが削除されました。substring()は、すべての文字を新しい文字列にコピーするだけです。

エルゴ、Java 7アップデート6では部分文字列はO(n)です


2
+1これは、最近のSunJavaおよびOpenJDKバージョンに実際に当てはまります。GNU Classpath(および他のクラスパス)はまだ古いパラダイムを使用しています。残念ながら、これには少し知的慣性があるようです。私はまだストリングが共有を使用するという仮定に基づいて様々なアプローチを推奨する2013年の記事を参照してくださいchar[]...
thkala

10
そのため、新しいバージョンではO(1)の複雑さがなくなりました。O(1)に部分文字列を実装する別の方法があるか知りたいですか?String.substringは非常に便利なメソッドです。
Yitong Zhou 2013

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

}

Java8での実行時の出力は次のとおりです。

 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)かどうかは、によって異なります。メモリ内の同じ文字列を参照するだけで、非常に長い文字列を想像すると、部分文字列を作成し、長い文字列の参照を停止します。長いもののためにメモリを解放するのはいいことではないでしょうか?


0

Java 1.7.0_06より前:O(1)。

のJava 1.7.0_06:O(N)。これは、メモリリークのために変更されました。フィールドoffsetcountが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.