web-dev-qa-db-ja.com

文字列内のすべての文字を反復処理する最速の方法

Javaでは、String内のすべての文字を反復処理する最も速い方法は次のとおりです。

String str = "a really, really long string";
for (int i = 0, n = str.length(); i < n; i++) {
    char c = str.charAt(i);
}

またはこれ:

char[] chars = str.toCharArray();
for (int i = 0, n = chars.length; i < n; i++) {
    char c = chars[i];
}

編集:

私が知りたいのは、長い反復中にcharAtメソッドを繰り返し呼び出すことのコストが、最初にtoCharArrayを1回呼び出してから配列に直接アクセスするコストよりも小さいか大きいかどうかです。繰り返し.

2つのSystem.currentTimeMillis()の呼び出しの違いだけでなく、JITのウォームアップ時間、JVMの起動時間などを念頭に置いて、誰かが異なる文字列の長さに対して堅牢なベンチマークを提供できるとしたら、それは素晴らしいことです。

144
Óscar López

最初の更新:実稼働環境(これは推奨されません)でこれを試す前に、まずこれを読んでください。 http://www.javaspecialists.eu/archive/Issue237.html Java 9以降、解決策説明されているように、Javaはデフォルトでbyte []として文字列を格納するようになったため、動作しなくなります。

2番目の更新:2016-10-25以降、私のAMDx64 8coreとソース1.8では、 'charAt'とフィールドアクセスの使用に違いはありません。 jvmは、 'string.charAt(n)'呼び出しをインライン化および合理化するために十分に最適化されているようです。

すべて検査対象のStringの長さによって異なります。もし質問が言うように、それがlongstringsのためであるなら、文字列を調べるための最も早い方法は文字列の裏付けchar[]にアクセスするためにリフレクションを使うことです。

64種類のAMD Phenom II 4コア955 @ 3.2GHz(クライアントモードとサーバーモードの両方)でJDK 8(win32およびwin64)を使用した9つの異なる手法(下記参照)で完全にランダム化されたベンチマークでは、String.charAt(n)を使用するのが最速です。小さい文字列や、文字列のバッキング配列にアクセスするためにreflectionを使用することは、大きい文字列の場合の約2倍の速さです。

実験

  • 9種類の最適化手法が試されています。

  • すべての文字列の内容はランダム化されています

  • テストは0、1、2、4、8、16などで始まる2の倍数の文字列サイズに対して行われます。

  • テストは文字列サイズあたり1,000回行われます

  • テストは毎回ランダムな順序でシャッフルされます。言い換えれば、テストは実行されるたびにランダムな順序で、1000回以上行われます。

  • テストスイート全体は、JVMのウォームアップが最適化と時間に与える影響を示すために、前後に行われます。

  • スイート全体が2回実行されます。1回は-clientモード、もう1回は-serverモードです。

結論

クライアントモード(32ビット)

文字列1〜256文字の長さの場合、string.charAt(i)を呼び出すと、1秒あたり平均1340万〜588万文字の処理で勝利します。

また、これは全体的に5.5%速く(クライアント)、13.9%(サーバー)です。

    for (int i = 0; i < data.length(); i++) {
        if (data.charAt(i) <= ' ') {
            doThrow();
        }
    }

ローカルの最終的な長さの変数でこれよりも好きです:

    final int len = data.length();
    for (int i = 0; i < len; i++) {
        if (data.charAt(i) <= ' ') {
            doThrow();
        }
    }

長い文字列の場合は、512から256K文字の長さを使用します。反射を使用して文字列のバッキング配列にアクセスするのが最も高速です。このテクニックはString.charAt(i)の2倍の速さです(178%高速)。この範囲の平均速度は、毎秒11億11000万文字でした。

Fieldは事前に取得しておく必要があります。その後、ライブラリで別の文字列に再利用できます。興味深いことに、上記のコードとは異なり、フィールドアクセスでは、ループチェックで 'chars.length'を使用するよりも、ローカルの最終的な長さ変数を使用する方が9%高速です。フィールドアクセスを最速に設定する方法は次のとおりです。

   final Field field = String.class.getDeclaredField("value");
   field.setAccessible(true);

   try {
       final char[] chars = (char[]) field.get(data);
       final int len = chars.length;
       for (int i = 0; i < len; i++) {
           if (chars[i] <= ' ') {
               doThrow();
           }
       }
       return len;
   } catch (Exception ex) {
       throw new RuntimeException(ex);
   }

-serverモードに関する特別なコメント

私のAMD 64マシンの64ビットJavaマシンでサーバモードで32文字の長さの文字列の後にフィールドアクセスが勝ち始めました。それはクライアントモードで512文字の長さまで見られませんでした。

サーバモードでJDK 8(32ビットビルド)を実行していたとき、全体的なパフォーマンスは大きな文字列と小さな文字列の両方で7%遅くなったと思います。これはJDK 8の早期リリースのビルド121 Dec 2013と一緒でした。だから、今のところ、32ビットサーバーモードは32ビットクライアントモードより遅いようです。

言われていること...それは呼び出す価値がある唯一のサーバーモードは64ビットマシン上にあるようです。さもなければそれは実際に性能を妨げます。

AMD64上の-server modeで実行されている32ビットビルドの場合、私はこれを言うことができます:

  1. String.charAt(i)が全体的に明らかな勝者です。サイズが8から512文字の間ですが、 'new'、 'reuse'、および 'field'に勝者がいました。
  2. String.charAt(i)はクライアントモードで45%高速です
  3. クライアントモードの大きな文字列の場合、フィールドアクセスは2倍高速です。

また、String.chars()(Streamおよびパラレルバージョン)はバストです。他の方法よりも遅い方法です。 Streams APIは一般的な文字列操作を実行するためのかなり遅い方法です。

欲しいものリスト

Java Stringは、contains(predicate)、forEach(consumer)、forEachWithIndex(consumer)などの最適化されたメソッドを受け入れる述語を持つことができます。したがって、ユーザーがStringメソッドの長さを知っていたり、Stringメソッドを繰り返し呼び出したりしなくても、ライブラリーのbeep-beep beepの高速化に役立ちます。

夢を見続ける :)

ハッピーストリングス!

〜SH

このテストでは、空白の存在を確認するために次の9つの方法で文字列をテストしました。

"charAt1" - 文字列が通常の方法であることを確認します。

int charAtMethod1(final String data) {
    final int len = data.length();
    for (int i = 0; i < len; i++) {
        if (data.charAt(i) <= ' ') {
            doThrow();
        }
    }
    return len;
}

"charAt2" - 長さのために最後のローカルintを作成する代わりにString.length()を使用する代わりに上記と同じ

int charAtMethod2(final String data) {
    for (int i = 0; i < data.length(); i++) {
        if (data.charAt(i) <= ' ') {
            doThrow();
        }
    }
    return data.length();
}

"stream" - 新しいJava-8 StringのIntStreamを使用し、それをチェックするための前提条件にします。

int streamMethod(final String data, final IntPredicate predicate) {
    if (data.chars().anyMatch(predicate)) {
        doThrow();
    }
    return data.length();
}

"streamPara" - 上記と同じ、ただしOH-LA-LA - GO GO PARALLEL!

// avoid this at all costs
int streamParallelMethod(final String data, IntPredicate predicate) {
    if (data.chars().parallel().anyMatch(predicate)) {
        doThrow();
    }
    return data.length();
}

"reuse" - 文字列の内容を含む再利用可能なchar []を補充します。

int reuseBuffMethod(final char[] reusable, final String data) {
    final int len = data.length();
    data.getChars(0, len, reusable, 0);
    for (int i = 0; i < len; i++) {
        if (reusable[i] <= ' ') {
            doThrow();
        }
    }
    return len;
}

"new1" - 文字列からchar []の新しいコピーを取得する

int newMethod1(final String data) {
    final int len = data.length();
    final char[] copy = data.toCharArray();
    for (int i = 0; i < len; i++) {
        if (copy[i] <= ' ') {
            doThrow();
        }
    }
    return len;
}

"new2" - 上記と同じ、ただし "FOR-EACH"を使用

int newMethod2(final String data) {
    for (final char c : data.toCharArray()) {
        if (c <= ' ') {
            doThrow();
        }
    }
    return data.length();
}

"field1" - ファンシー!文字列の内部文字へのアクセスのための取得フィールド

int fieldMethod1(final Field field, final String data) {
    try {
        final char[] chars = (char[]) field.get(data);
        final int len = chars.length;
        for (int i = 0; i < len; i++) {
            if (chars[i] <= ' ') {
                doThrow();
            }
        }
        return len;
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

"field2" - 上記と同じ、ただし "FOR-EACH"を使用

int fieldMethod2(final Field field, final String data) {
    final char[] chars;
    try {
        chars = (char[]) field.get(data);
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
    for (final char c : chars) {
        if (c <= ' ') {
            doThrow();
        }
    }
    return chars.length;
}

クライアントのコンポジット結果-clientモード(順方向テストと逆方向テストの組み合わせ)

注:Java 32ビットの-clientモードおよびJava 64ビットの-serverモードは、私のAMD64マシンでは以下と同じです。

Size     WINNER  charAt1 charAt2  stream streamPar   reuse    new1    new2  field1  field2
1        charAt    77.0     72.0   462.0     584.0   127.5    89.5    86.0   159.5   165.0
2        charAt    38.0     36.5   284.0   32712.5    57.5    48.3    50.3    89.0    91.5
4        charAt    19.5     18.5   458.6    3169.0    33.0    26.8    27.5    54.1    52.6
8        charAt     9.8      9.9   100.5    1370.9    17.3    14.4    15.0    26.9    26.4
16       charAt     6.1      6.5    73.4     857.0     8.4     8.2     8.3    13.6    13.5
32       charAt     3.9      3.7    54.8     428.9     5.0     4.9     4.7     7.0     7.2
64       charAt     2.7      2.6    48.2     232.9     3.0     3.2     3.3     3.9     4.0
128      charAt     2.1      1.9    43.7     138.8     2.1     2.6     2.6     2.4     2.6
256      charAt     1.9      1.6    42.4      90.6     1.7     2.1     2.1     1.7     1.8
512      field1     1.7      1.4    40.6      60.5     1.4     1.9     1.9     1.3     1.4
1,024    field1     1.6      1.4    40.0      45.6     1.2     1.9     2.1     1.0     1.2
2,048    field1     1.6      1.3    40.0      36.2     1.2     1.8     1.7     0.9     1.1
4,096    field1     1.6      1.3    39.7      32.6     1.2     1.8     1.7     0.9     1.0
8,192    field1     1.6      1.3    39.6      30.5     1.2     1.8     1.7     0.9     1.0
16,384   field1     1.6      1.3    39.8      28.4     1.2     1.8     1.7     0.8     1.0
32,768   field1     1.6      1.3    40.0      26.7     1.3     1.8     1.7     0.8     1.0
65,536   field1     1.6      1.3    39.8      26.3     1.3     1.8     1.7     0.8     1.0
131,072  field1     1.6      1.3    40.1      25.4     1.4     1.9     1.8     0.8     1.0
262,144  field1     1.6      1.3    39.6      25.2     1.5     1.9     1.9     0.8     1.0

サーバーの-serverモードの複合結果(順方向および逆方向テストを組み合わせたもの)

注:これは、AMD64上でサーバーモードで動作するJava 32ビットのテストです。 Java 64ビット用のサーバーモードは、フィールドモードアクセスが32文字のサイズの後に勝つことを除いて、クライアントモードのJava 32ビットと同じでした。

Size     WINNER  charAt1 charAt2  stream streamPar   reuse    new1    new2  field1  field2
1        charAt     74.5    95.5   524.5     783.0    90.5   102.5    90.5   135.0   151.5
2        charAt     48.5    53.0   305.0   30851.3    59.3    57.5    52.0    88.5    91.8
4        charAt     28.8    32.1   132.8    2465.1    37.6    33.9    32.3    49.0    47.0
8          new2     18.0    18.6    63.4    1541.3    18.5    17.9    17.6    25.4    25.8
16         new2     14.0    14.7   129.4    1034.7    12.5    16.2    12.0    16.0    16.6
32         new2      7.8     9.1    19.3     431.5     8.1     7.0     6.7     7.9     8.7
64        reuse      6.1     7.5    11.7     204.7     3.5     3.9     4.3     4.2     4.1
128       reuse      6.8     6.8     9.0     101.0     2.6     3.0     3.0     2.6     2.7
256      field2      6.2     6.5     6.9      57.2     2.4     2.7     2.9     2.3     2.3
512       reuse      4.3     4.9     5.8      28.2     2.0     2.6     2.6     2.1     2.1
1,024    charAt      2.0     1.8     5.3      17.6     2.1     2.5     3.5     2.0     2.0
2,048    charAt      1.9     1.7     5.2      11.9     2.2     3.0     2.6     2.0     2.0
4,096    charAt      1.9     1.7     5.1       8.7     2.1     2.6     2.6     1.9     1.9
8,192    charAt      1.9     1.7     5.1       7.6     2.2     2.5     2.6     1.9     1.9
16,384   charAt      1.9     1.7     5.1       6.9     2.2     2.5     2.5     1.9     1.9
32,768   charAt      1.9     1.7     5.1       6.1     2.2     2.5     2.5     1.9     1.9
65,536   charAt      1.9     1.7     5.1       5.5     2.2     2.4     2.4     1.9     1.9
131,072  charAt      1.9     1.7     5.1       5.4     2.3     2.5     2.5     1.9     1.9
262,144  charAt      1.9     1.7     5.1       5.1     2.3     2.5     2.5     1.9     1.9

フルランナブルプログラムコード

(Java 7以前でテストするには、2つのストリームテストを削除してください)

import Java.lang.reflect.Field;
import Java.util.ArrayList;
import Java.util.Collections;
import Java.util.List;
import Java.util.Random;
import Java.util.function.IntPredicate;

/**
 * @author Saint Hill <http://stackoverflow.com/users/1584255/saint-hill>
 */
public final class TestStrings {

    // we will not test strings longer than 512KM
    final int MAX_STRING_SIZE = 1024 * 256;

    // for each string size, we will do all the tests
    // this many times
    final int TRIES_PER_STRING_SIZE = 1000;

    public static void main(String[] args) throws Exception {
        new TestStrings().run();
    }

    void run() throws Exception {

        // double the length of the data until it reaches MAX chars long
        // 0,1,2,4,8,16,32,64,128,256 ... 
        final List<Integer> sizes = new ArrayList<>();
        for (int n = 0; n <= MAX_STRING_SIZE; n = (n == 0 ? 1 : n * 2)) {
            sizes.add(n);
        }

        // CREATE RANDOM (FOR SHUFFLING ORDER OF TESTS)
        final Random random = new Random();

        System.out.println("Rate in nanoseconds per character inspected.");
        System.out.printf("==== FORWARDS (tries per size: %s) ==== \n", TRIES_PER_STRING_SIZE);

        printHeadings(TRIES_PER_STRING_SIZE, random);

        for (int size : sizes) {
            reportResults(size, test(size, TRIES_PER_STRING_SIZE, random));
        }

        // reverse order or string sizes
        Collections.reverse(sizes);

        System.out.println("");
        System.out.println("Rate in nanoseconds per character inspected.");
        System.out.printf("==== BACKWARDS (tries per size: %s) ==== \n", TRIES_PER_STRING_SIZE);

        printHeadings(TRIES_PER_STRING_SIZE, random);

        for (int size : sizes) {
            reportResults(size, test(size, TRIES_PER_STRING_SIZE, random));

        }
    }

    ///
    ///
    ///  METHODS OF CHECKING THE CONTENTS
    ///  OF A STRING. ALWAYS CHECKING FOR
    ///  WHITESPACE (CHAR <=' ')
    ///  
    ///
    // CHECK THE STRING CONTENTS
    int charAtMethod1(final String data) {
        final int len = data.length();
        for (int i = 0; i < len; i++) {
            if (data.charAt(i) <= ' ') {
                doThrow();
            }
        }
        return len;
    }

    // SAME AS ABOVE BUT USE String.length()
    // instead of making a new final local int 
    int charAtMethod2(final String data) {
        for (int i = 0; i < data.length(); i++) {
            if (data.charAt(i) <= ' ') {
                doThrow();
            }
        }
        return data.length();
    }

    // USE new Java-8 String's IntStream
    // pass it a PREDICATE to do the checking
    int streamMethod(final String data, final IntPredicate predicate) {
        if (data.chars().anyMatch(predicate)) {
            doThrow();
        }
        return data.length();
    }

    // OH LA LA - GO PARALLEL!!!
    int streamParallelMethod(final String data, IntPredicate predicate) {
        if (data.chars().parallel().anyMatch(predicate)) {
            doThrow();
        }
        return data.length();
    }

    // Re-fill a resuable char[] with the contents
    // of the String's char[]
    int reuseBuffMethod(final char[] reusable, final String data) {
        final int len = data.length();
        data.getChars(0, len, reusable, 0);
        for (int i = 0; i < len; i++) {
            if (reusable[i] <= ' ') {
                doThrow();
            }
        }
        return len;
    }

    // Obtain a new copy of char[] from String
    int newMethod1(final String data) {
        final int len = data.length();
        final char[] copy = data.toCharArray();
        for (int i = 0; i < len; i++) {
            if (copy[i] <= ' ') {
                doThrow();
            }
        }
        return len;
    }

    // Obtain a new copy of char[] from String
    // but use FOR-EACH
    int newMethod2(final String data) {
        for (final char c : data.toCharArray()) {
            if (c <= ' ') {
                doThrow();
            }
        }
        return data.length();
    }

    // FANCY!
    // OBTAIN FIELD FOR ACCESS TO THE STRING'S
    // INTERNAL CHAR[]
    int fieldMethod1(final Field field, final String data) {
        try {
            final char[] chars = (char[]) field.get(data);
            final int len = chars.length;
            for (int i = 0; i < len; i++) {
                if (chars[i] <= ' ') {
                    doThrow();
                }
            }
            return len;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    // same as above but use FOR-EACH
    int fieldMethod2(final Field field, final String data) {
        final char[] chars;
        try {
            chars = (char[]) field.get(data);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
        for (final char c : chars) {
            if (c <= ' ') {
                doThrow();
            }
        }
        return chars.length;
    }

    /**
     *
     * Make a list of tests. We will shuffle a copy of this list repeatedly
     * while we repeat this test.
     *
     * @param data
     * @return
     */
    List<Jobber> makeTests(String data) throws Exception {
        // make a list of tests
        final List<Jobber> tests = new ArrayList<Jobber>();

        tests.add(new Jobber("charAt1") {
            int check() {
                return charAtMethod1(data);
            }
        });

        tests.add(new Jobber("charAt2") {
            int check() {
                return charAtMethod2(data);
            }
        });

        tests.add(new Jobber("stream") {
            final IntPredicate predicate = new IntPredicate() {
                public boolean test(int value) {
                    return value <= ' ';
                }
            };

            int check() {
                return streamMethod(data, predicate);
            }
        });

        tests.add(new Jobber("streamPar") {
            final IntPredicate predicate = new IntPredicate() {
                public boolean test(int value) {
                    return value <= ' ';
                }
            };

            int check() {
                return streamParallelMethod(data, predicate);
            }
        });

        // Reusable char[] method
        tests.add(new Jobber("reuse") {
            final char[] cbuff = new char[MAX_STRING_SIZE];

            int check() {
                return reuseBuffMethod(cbuff, data);
            }
        });

        // New char[] from String
        tests.add(new Jobber("new1") {
            int check() {
                return newMethod1(data);
            }
        });

        // New char[] from String
        tests.add(new Jobber("new2") {
            int check() {
                return newMethod2(data);
            }
        });

        // Use reflection for field access
        tests.add(new Jobber("field1") {
            final Field field;

            {
                field = String.class.getDeclaredField("value");
                field.setAccessible(true);
            }

            int check() {
                return fieldMethod1(field, data);
            }
        });

        // Use reflection for field access
        tests.add(new Jobber("field2") {
            final Field field;

            {
                field = String.class.getDeclaredField("value");
                field.setAccessible(true);
            }

            int check() {
                return fieldMethod2(field, data);
            }
        });

        return tests;
    }

    /**
     * We use this class to keep track of test results
     */
    abstract class Jobber {

        final String name;
        long nanos;
        long chars;
        long runs;

        Jobber(String name) {
            this.name = name;
        }

        abstract int check();

        final double nanosPerChar() {
            double charsPerRun = chars / runs;
            long nanosPerRun = nanos / runs;
            return charsPerRun == 0 ? nanosPerRun : nanosPerRun / charsPerRun;
        }

        final void run() {
            runs++;
            long time = System.nanoTime();
            chars += check();
            nanos += System.nanoTime() - time;
        }
    }

    // MAKE A TEST STRING OF RANDOM CHARACTERS A-Z
    private String makeTestString(int testSize, char start, char end) {
        Random r = new Random();
        char[] data = new char[testSize];
        for (int i = 0; i < data.length; i++) {
            data[i] = (char) (start + r.nextInt(end));
        }
        return new String(data);
    }

    // WE DO THIS IF WE FIND AN ILLEGAL CHARACTER IN THE STRING
    public void doThrow() {
        throw new RuntimeException("Bzzzt -- Illegal Character!!");
    }

    /**
     * 1. get random string of correct length 2. get tests (List<Jobber>) 3.
     * perform tests repeatedly, shuffling each time
     */
    List<Jobber> test(int size, int tries, Random random) throws Exception {
        String data = makeTestString(size, 'A', 'Z');
        List<Jobber> tests = makeTests(data);
        List<Jobber> copy = new ArrayList<>(tests);
        while (tries-- > 0) {
            Collections.shuffle(copy, random);
            for (Jobber ti : copy) {
                ti.run();
            }
        }
        // check to make sure all char counts the same
        long runs = tests.get(0).runs;
        long count = tests.get(0).chars;
        for (Jobber ti : tests) {
            if (ti.runs != runs && ti.chars != count) {
                throw new Exception("Char counts should match if all correct algorithms");
            }
        }
        return tests;
    }

    private void printHeadings(final int TRIES_PER_STRING_SIZE, final Random random) throws Exception {
        System.out.print("  Size");
        for (Jobber ti : test(0, TRIES_PER_STRING_SIZE, random)) {
            System.out.printf("%9s", ti.name);
        }
        System.out.println("");
    }

    private void reportResults(int size, List<Jobber> tests) {
        System.out.printf("%6d", size);
        for (Jobber ti : tests) {
            System.out.printf("%,9.2f", ti.nanosPerChar());
        }
        System.out.println("");
    }
}
336
The Coordinator

これは、あなたが心配してはいけない単なるマイクロ最適化です。

char[] chars = str.toCharArray();

str文字配列のコピーを返します(JDKでは、System.arrayCopyを呼び出すことによって文字のコピーを返します)。

それ以外の場合、str.charAt()は、インデックスが実際に境界内にあるかどうかをチェックし、配列インデックス内の文字を返します。

最初のものはJVMで追加のメモリを作成しません。

13
Buhake Sindi

好奇心のためだけにセントヒルの答えと比較するために

大量のデータを処理する必要がある場合は、クライアントモードでJVMを使用しないでください。クライアントモードは最適化のために作られていません。

クライアントモードとサーバーモードのJVMを使用して@Saint Hillベンチマークの結果を比較しましょう。

Core2Quad Q6600 G0 @ 2.4GHz
JavaSE 1.7.0_40

参照: "Java -server"と "Java -client"の実際の違いは?


クライアントモード:

len =      2:    111k charAt(i),  105k cbuff[i],   62k new[i],   17k field access.   (chars/ms) 
len =      4:    285k charAt(i),  166k cbuff[i],  114k new[i],   43k field access.   (chars/ms) 
len =      6:    315k charAt(i),  230k cbuff[i],  162k new[i],   69k field access.   (chars/ms) 
len =      8:    333k charAt(i),  275k cbuff[i],  181k new[i],   85k field access.   (chars/ms) 
len =     12:    342k charAt(i),  342k cbuff[i],  222k new[i],  117k field access.   (chars/ms) 
len =     16:    363k charAt(i),  347k cbuff[i],  275k new[i],  152k field access.   (chars/ms) 
len =     20:    363k charAt(i),  392k cbuff[i],  289k new[i],  180k field access.   (chars/ms) 
len =     24:    375k charAt(i),  428k cbuff[i],  311k new[i],  205k field access.   (chars/ms) 
len =     28:    378k charAt(i),  474k cbuff[i],  341k new[i],  233k field access.   (chars/ms) 
len =     32:    376k charAt(i),  492k cbuff[i],  340k new[i],  251k field access.   (chars/ms) 
len =     64:    374k charAt(i),  551k cbuff[i],  374k new[i],  367k field access.   (chars/ms) 
len =    128:    385k charAt(i),  624k cbuff[i],  415k new[i],  509k field access.   (chars/ms) 
len =    256:    390k charAt(i),  675k cbuff[i],  436k new[i],  619k field access.   (chars/ms) 
len =    512:    394k charAt(i),  703k cbuff[i],  439k new[i],  695k field access.   (chars/ms) 
len =   1024:    395k charAt(i),  718k cbuff[i],  462k new[i],  742k field access.   (chars/ms) 
len =   2048:    396k charAt(i),  725k cbuff[i],  471k new[i],  767k field access.   (chars/ms) 
len =   4096:    396k charAt(i),  727k cbuff[i],  459k new[i],  780k field access.   (chars/ms) 
len =   8192:    397k charAt(i),  712k cbuff[i],  446k new[i],  772k field access.   (chars/ms) 

サーバーモード:

len =      2:     86k charAt(i),   41k cbuff[i],   46k new[i],   80k field access.   (chars/ms) 
len =      4:    571k charAt(i),  250k cbuff[i],   97k new[i],  222k field access.   (chars/ms) 
len =      6:    666k charAt(i),  333k cbuff[i],  125k new[i],  315k field access.   (chars/ms) 
len =      8:    800k charAt(i),  400k cbuff[i],  181k new[i],  380k field access.   (chars/ms) 
len =     12:    800k charAt(i),  521k cbuff[i],  260k new[i],  545k field access.   (chars/ms) 
len =     16:    800k charAt(i),  592k cbuff[i],  296k new[i],  640k field access.   (chars/ms) 
len =     20:    800k charAt(i),  666k cbuff[i],  408k new[i],  800k field access.   (chars/ms) 
len =     24:    800k charAt(i),  705k cbuff[i],  452k new[i],  800k field access.   (chars/ms) 
len =     28:    777k charAt(i),  736k cbuff[i],  368k new[i],  933k field access.   (chars/ms) 
len =     32:    800k charAt(i),  780k cbuff[i],  571k new[i],  969k field access.   (chars/ms) 
len =     64:    800k charAt(i),  901k cbuff[i],  800k new[i],  1306k field access.   (chars/ms) 
len =    128:    1084k charAt(i),  888k cbuff[i],  633k new[i],  1620k field access.   (chars/ms) 
len =    256:    1122k charAt(i),  966k cbuff[i],  729k new[i],  1790k field access.   (chars/ms) 
len =    512:    1163k charAt(i),  1007k cbuff[i],  676k new[i],  1910k field access.   (chars/ms) 
len =   1024:    1179k charAt(i),  1027k cbuff[i],  698k new[i],  1954k field access.   (chars/ms) 
len =   2048:    1184k charAt(i),  1043k cbuff[i],  732k new[i],  2007k field access.   (chars/ms) 
len =   4096:    1188k charAt(i),  1049k cbuff[i],  742k new[i],  2031k field access.   (chars/ms) 
len =   8192:    1157k charAt(i),  1032k cbuff[i],  723k new[i],  2048k field access.   (chars/ms) 

結論:

ご覧のとおり、サーバーモードははるかに高速です。

9
ceklock

str.charAtを使う最初の方が速いはずです。

Stringクラスのソースコードを掘り下げると、charAtが次のように実装されていることがわかります。

public char charAt(int index) {
    if ((index < 0) || (index >= count)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index + offset];
}

ここでは、配列にインデックスを付けて値を返すだけです。

さて、toCharArrayの実装を見ると、以下のようになります。

public char[] toCharArray() {
    char result[] = new char[count];
    getChars(0, count, result, 0);
    return result;
}

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    if (srcBegin < 0) {
        throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > count) {
        throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd) {
        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, offset + srcBegin, dst, dstBegin,
         srcEnd - srcBegin);
}

見ての通り、それはSystem.arraycopyをやっています。それは間違いなくそれをしないより少し遅くなるでしょう。

6
adarshr

str.toCharArray()という時間の複雑さを考慮すると、@Saint Hillの答えにもかかわらず、

最初のものは非常に大きな文字列でも速いです。あなたはそれを自分で見るために以下のコードを実行することができます。

        char [] ch = new char[1_000_000_00];
    String str = new String(ch); // to create a large string

    // ---> from here
    long currentTime = System.nanoTime();
    for (int i = 0, n = str.length(); i < n; i++) {
        char c = str.charAt(i);
    }
    // ---> to here
    System.out.println("str.charAt(i):"+(System.nanoTime()-currentTime)/1000000.0 +" (ms)");

    /**
     *   ch = str.toCharArray() itself takes lots of time   
     */
    // ---> from here
    currentTime = System.nanoTime();
    ch = str.toCharArray();
    for (int i = 0, n = str.length(); i < n; i++) {
        char c = ch[i];
    }
    // ---> to  here
    System.out.println("ch = str.toCharArray() + c = ch[i] :"+(System.nanoTime()-currentTime)/1000000.0 +" (ms)");

出力:

str.charAt(i):5.492102 (ms)
ch = str.toCharArray() + c = ch[i] :79.400064 (ms)
2
C graphics

String.toCharArray()は、新しいchar配列を作成し、文字列長のメモリを割り当て、次にSystem.arraycopy()を使用して文字列の元のchar配列をコピーして、このコピーを呼び出し元に返します。 String.charAt()は元のコピーからiの位置にある文字を返すため、String.charAt()String.toCharArray()よりも高速になります。ただし、String.toCharArray()は元のString配列からcharではなくcopyを返します。ここで、String.charAt()は元のchar配列から文字を返します。以下のコードは、この文字列の指定されたインデックスの値を返します。

public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

以下のコードは、長さがこの文字列の長さである、新しく割り当てられた文字配列を返します。

public char[] toCharArray() {
    // Cannot use Arrays.copyOf because of class initialization order issues
    char result[] = new char[value.length];
    System.arraycopy(value, 0, result, 0, value.length);
    return result;
}
2
viki s

より速いか遅いかのように見える

    public static void main(String arguments[]) {


        //Build a long string
        StringBuilder sb = new StringBuilder();
        for(int j = 0; j < 10000; j++) {
            sb.append("a really, really long string");
        }
        String str = sb.toString();
        for (int testscount = 0; testscount < 10; testscount ++) {


            //Test 1
            long start = System.currentTimeMillis();
            for(int c = 0; c < 10000000; c++) {
                for (int i = 0, n = str.length(); i < n; i++) {
                    char chr = str.charAt(i);
                    doSomethingWithChar(chr);//To trick JIT optimistaion
                }
            }

            System.out.println("1: " + (System.currentTimeMillis() - start));

            //Test 2
            start = System.currentTimeMillis();
            char[] chars = str.toCharArray();
            for(int c = 0; c < 10000000; c++) {
                for (int i = 0, n = chars.length; i < n; i++) {
                    char chr = chars[i];
                    doSomethingWithChar(chr);//To trick JIT optimistaion
                }
            }
            System.out.println("2: " + (System.currentTimeMillis() - start));
            System.out.println();
        }


    }


    public static void doSomethingWithChar(char chr) {
        int newInt = chr << 2;
    }

長い弦の場合は最初のものを選びます。長い文字列をコピーするのはなぜですか?ドキュメンテーションは言う:

public char [] toCharArray()この文字列を新しい文字配列に変換します。

戻り値:長さがこの文字列の長さで、その内容がこの文字列で表される文字シーケンスを含むように初期化された、新しく割り当てられた文字配列。

//編集1

私はテストをトリックJIT最適化に変更しました。

//編集2

JVMをウォームアップするためにテストを10回繰り返します。

//編集

結論:

まずstr.toCharArray();は文字列全体をメモリにコピーします。長い文字列ではメモリを消費する可能性があります。メソッドString.charAt( )は、文字列クラス内のchar配列内のcharを調べてインデックスを調べます。十分に短い文字列の最初の方法(すなわちchatAtメソッド)は、このインデックスチェックのために少し遅くなります。しかし、Stringが十分に長い場合は、char配列全体のコピーが遅くなり、最初の方法の方が速くなります。文字列が長いほど、toCharArrayの実行速度が遅くなります。それを見るためにfor(int j = 0; j < 10000; j++)ループの制限を変更しようとしてください。 JVMのウォームアップコードの実行速度を速くしても、比率は同じです。

結局のところ、それは単なるマイクロ最適化です。

2
Piotr Gwiazda

2番目のものは新しいchar配列を作成し、Stringからのすべての文字はこの新しいchar配列にコピーされるので、最初のものはより速い(そしてメモリをあまり必要としない)と思います。

1
JB Nizet