문자열의 모든 문자를 반복하는 가장 빠른 방법 = str.toCharArray(); for (int

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처음에 단일 호출을 수행 한 다음 반복하는 동안 배열에 직접 액세스하는 비용보다 적거나 큰 경우 입니다.

JIT 예열 시간, JVM 시작 시간 등을 염두에두고 두 개의 호출의 차이점을 염두에두고 다른 문자열 길이에 대해 강력한 벤치 마크를 제공 할 수 있다면 좋을 것 System.currentTimeMillis()입니다.



답변

첫 번째 업데이트 : 프로덕션 환경에서이 작업을 시도하기 전에 (권장하지 않음) 먼저 다음을 읽으십시오. http://www.javaspecialists.eu/archive/Issue237.html
Java 9부터는 설명 된 솔루션이 더 이상 작동하지 않습니다. Java는 기본적으로 문자열을 byte []로 저장하기 때문입니다.

두 번째 업데이트 : 2016-10-25 현재, AMDx64 8core 및 소스 1.8에서 ‘charAt’과 필드 액세스를 사용하는 것에는 차이가 없습니다. jvm은 ‘string.charAt (n)’호출을 인라인하고 간소화하기에 충분히 최적화 된 것으로 보입니다.

그것은 모두 String검사되는 길이에 달려 있습니다. 질문에서 알 수 있듯이 문자열에 대한 것이면 문자열을 검사하는 가장 빠른 방법은 리플렉션을 사용 char[]하여 문자열 의 뒷면 에 액세스하는 것 입니다.

9 개 가지 기술을 (두 클라이언트 모드와 서버 모드)를 64 AMD 페넘 II 4 코어에 JDK 8 (Win32 및 Win64를)와 완전히 무작위 벤치 마크 955 @ 3.2 GHZ (아래 참조!)를 사용하는 것을 알 수 String.charAt(n)있는 가장 빠른 작은을위한입니다 문자열 및 reflectionString 백업 배열에 액세스 하는 데 사용 하는 것이 큰 문자열의 경우 거의 두 배 빠릅니다.

실험

  • 9 가지 최적화 기법이 시도되었습니다.

  • 모든 문자열 내용은 무작위입니다

  • 테스트는 0,1,2,4,8,16 등으로 시작하는 2의 배수로 문자열 크기에 대해 수행됩니다.

  • 테스트는 문자열 크기 당 1,000 회 수행됩니다.

  • 테스트는 매번 무작위 순서로 섞입니다. 다시 말해, 테스트는 매번 1000 번 이상 수행 될 때마다 무작위 순서로 수행됩니다.

  • 전체 테스트 스위트는 JVM 워밍업이 최적화 및 시간에 미치는 영향을 보여주기 위해 앞뒤로 수행됩니다.

  • 전체 제품군은 한 번은 -client모드에서, 다른 하나는 -server모드 에서 두 번 수행 됩니다.

결론

클라이언트 모드 (32 비트)

문자열 의 길이는 1 256 자 호출, string.charAt(i)초당 13,400,000 만 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) 보다 거의 두 배 빠릅니다 (178 % 빠름). 이 범위의 평균 속도는 초당 11.111 억 자입니다.

필드는 미리 확보 한 다음 라이브러리에서 다른 문자열로 재사용 할 수 있습니다. 흥미롭게도, 위의 코드와 달리 필드 액세스를 사용하면 루프 검사에서 ‘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);
   }

서버 모드에 대한 특별 설명

AMD 64 시스템의 64 비트 Java 시스템에서 서버 모드에서 32 자 길이의 문자열 이후에 필드 액세스가 시작됩니다. 클라이언트 모드에서 512 자 길이까지 표시되지 않았습니다.

또한 서버 모드에서 JDK 8 (32 비트 빌드)을 실행할 때 전체 문자열이 작은 문자열과 작은 문자열 모두에서 7 % 느려 졌다고 생각합니다. 이것은 JDK 8 초기 릴리스의 2013 년 12 월 121 빌드입니다. 따라서 현재로서는 32 비트 서버 모드가 32 비트 클라이언트 모드보다 느린 것 같습니다.

말하자면 … 호출 할 가치가있는 유일한 서버 모드는 64 비트 시스템에있는 것 같습니다. 그렇지 않으면 실제로 성능이 저하됩니다.

-server modeAMD64 에서 실행되는 32 비트 빌드의 경우 다음과 같이 말할 수 있습니다.

  1. String.charAt (i)는 전체적으로 확실한 승자입니다. 크기는 8-512 자 사이이지만 ‘신규’ ‘재사용’및 ‘필드’에서 승자가있었습니다.
  2. 클라이언트 모드에서 String.charAt (i)가 45 % 빠릅니다.
  3. 클라이언트 모드에서 큰 문자열의 경우 필드 액세스가 두 배 빠릅니다.

또한 String.chars () (스트림 및 병렬 버전)는 흉상입니다. 다른 방법보다 속도가 느립니다. StreamsAPI는 일반 문자열 연산을 수행하는 다소 느린 방법입니다.

위시리스트

Java String은 contains (predicate), forEach (consumer), forEachWithIndex (consumer)와 같은 최적화 된 메소드를 허용하는 술어를 가질 수 있습니다. 따라서 사용자가 String 메서드의 길이 또는 반복 호출을 알 필요없이 라이브러리 구문 분석 beep-beep beep속도를 높이는 데 도움이 될 수 있습니다.

계속 꿈꾸세요 🙂

행복한 줄!

~ SH

이 테스트에서는 공백이 있는지 문자열을 테스트하는 다음 9 가지 방법을 사용했습니다.

“charAt1”-STRING 콘텐츠를 점검하십시오. 일반적인 방법 :

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”-위와 동일하지만 String.length () 길이에 대한 최종 로컬 int 만들기

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을 사용하고 점검을 위해 Predicate를 전달하십시오

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

“streamPara”-위와 동일하지만 OH-LA-LA-GO PARALLEL !!!

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

“재사용”-문자열이 포함 된 재사용 가능한 문자를 리필합니다.

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”-문자열에서 새 문자를 가져옵니다 []

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”-팬시 !! STRING의 내부 문자에 액세스하기위한 필드 가져 오기 []

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 비트의 서버 모드는 클라이언트 모드의 Java 32 비트와 동일하며 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 및 이전 버전에서 테스트하려면 두 개의 스트림 테스트를 제거하십시오)

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("");
    }
}

답변

이것은 걱정할 필요가없는 미세 최적화입니다.

char[] chars = str.toCharArray();

str문자 배열 의 사본을 리턴합니다 (JDK에서는을 호출하여 문자 사본을 리턴 함 System.arrayCopy).

그 외에는 str.charAt()인덱스가 실제로 경계 내에 있는지 확인하고 배열 인덱스 내의 문자를 반환합니다.

첫 번째는 JVM에서 추가 메모리를 생성하지 않습니다.


답변

호기심과 세인트 힐의 답변과 비교하십시오.

무거운 데이터를 처리해야하는 경우 클라이언트 모드에서 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) 

결론:

보다시피 서버 모드가 훨씬 빠릅니다.


답변

처음 사용하는 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하지 않는 것보다 느리게 진행되고 있습니다.


답변

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)

답변

니더가 더 빠르거나 느린 것 같습니다

    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 번 반복하십시오.

// 3 수정

결론 :

우선 str.toCharArray();전체 문자열을 메모리에 복사합니다. 긴 문자열에는 메모리를 소비 할 수 있습니다. 메소드 String.charAt( )는 이전에 String 클래스 검사 색인에서 char 배열에서 char을 찾습니다. 충분히 짧은 문자열처럼 보입니다. 첫 번째 방법 (즉, chatAt방법) 은이 색인 확인으로 인해 조금 느립니다. 그러나 문자열이 충분히 길면 전체 문자 배열을 복사하는 속도가 느려지고 첫 번째 방법이 더 빠릅니다. 줄이 길수록 toCharArray연주 속도가 느려집니다 . for(int j = 0; j < 10000; j++)루프에서 제한을 변경하여보십시오 . JVM 워밍업 코드가 더 빨리 실행되도록하지만 비율은 동일합니다.

결국 그것은 단지 미세 최적화입니다.


답변

String.toCharArray()새로운 문자 배열을 생성하고, 문자열 길이의 메모리 할당을 의미 한 다음 문자열을 사용하여 원래 문자 배열을 System.arraycopy()복사 한 다음이 사본을 호출자에게 반환합니다. 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;
}