Cara tercepat untuk beralih ke semua karakter dalam sebuah String

163

Di Jawa, apa cara tercepat untuk mengulangi semua karakter dalam sebuah String, ini:

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

Atau ini:

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

EDIT:

Yang ingin saya ketahui adalah jika biaya berulang kali memanggil charAtmetode selama iterasi yang panjang berakhir menjadi kurang dari atau lebih besar dari biaya melakukan satu panggilan toCharArraydi awal dan kemudian secara langsung mengakses array selama iterasi.

Akan lebih bagus jika seseorang dapat memberikan tolok ukur yang kuat untuk panjang string yang berbeda, mengingat waktu pemanasan JIT, waktu memulai JVM, dll. Dan bukan hanya perbedaan antara dua panggilan System.currentTimeMillis().

Óscar López
sumber
18
Apa yang terjadi for (char c : chars)?
dasblinkenlight
Yang pertama harus lebih cepat, dan lagi pula string array char, secara teoritis.
Keagan Ladds
Google sering merupakan sumber yang bagus: mkyong.com/java/…
Johan Sjöberg
2
Pertanyaannya tidak menanyakan kinerja penggunaan iterators, foreach. Yang ingin saya ketahui adalah apakah biaya panggilan berulang kali charAtmenjadi kurang dari atau lebih besar dari biaya melakukan satu panggilan ketoCharArray
Óscar López
1
Adakah yang melakukan analisis dengan StringCharacterIterator ?
bdrx

Jawaban:

352

PEMBARUAN PERTAMA: Sebelum Anda mencoba ini di lingkungan produksi (tidak disarankan), baca ini dulu: http://www.javaspecialists.eu/archive/Issue237.html Mulai dari Jawa 9, solusi seperti yang dijelaskan tidak akan berfungsi lagi , karena sekarang Java akan menyimpan string sebagai byte [] secara default.

UPDATE KEDUA: Pada 2016-10-25, pada AMDx64 8core saya dan sumber 1.8, tidak ada perbedaan antara menggunakan 'charAt' dan akses lapangan. Tampaknya jvm cukup optimal untuk menyejajarkan dan merampingkan panggilan 'string.charAt (n)'.

Itu semua tergantung pada lama Stringdiperiksa. Jika, seperti yang dikatakan pertanyaan, itu untuk string panjang , cara tercepat untuk memeriksa string adalah dengan menggunakan refleksi untuk mengakses dukungan char[]dari string.

Patokan acak lengkap dengan JDK 8 (win32 dan win64) pada 64 AMD Phenom II 4 core 955 @ 3,2 GHZ (dalam mode klien dan mode server) dengan 9 teknik berbeda (lihat di bawah!) Menunjukkan bahwa menggunakan String.charAt(n)adalah yang tercepat untuk kecil string dan menggunakan reflectionuntuk mengakses array dukungan String hampir dua kali lebih cepat untuk string besar.

PERCOBAAN

  • 9 teknik optimasi yang berbeda dicoba.

  • Semua konten string diacak

  • Tes dilakukan untuk ukuran string dalam kelipatan dua mulai dengan 0,1,2,4,8,16 dll.

  • Pengujian dilakukan 1.000 kali per ukuran string

  • Tes dikocok secara acak setiap kali. Dengan kata lain, tes dilakukan secara acak setiap kali dilakukan, lebih dari 1000 kali lipat.

  • Seluruh rangkaian uji dilakukan ke depan, dan ke belakang, untuk menunjukkan efek pemanasan JVM pada optimasi dan waktu.

  • Seluruh rangkaian dilakukan dua kali, sekali dalam -clientmode dan lainnya dalam -servermode.

KESIMPULAN

-client mode (32 bit)

Untuk string yang panjangnya 1 hingga 256 karakter , menelepon string.charAt(i)menang dengan pemrosesan rata-rata 13,4 juta hingga 588 juta karakter per detik.

Juga, secara keseluruhan 5,5% lebih cepat (klien) dan 13,9% (server) seperti ini:

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

daripada seperti ini dengan variabel panjang akhir lokal:

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

Untuk string panjang, panjang 512 hingga 256 ribu karakter , menggunakan pantulan untuk mengakses array backing String paling cepat. Teknik ini hampir dua kali lebih cepat dari String.charAt (i) (178% lebih cepat). Kecepatan rata-rata pada kisaran ini adalah 1,111 miliar karakter per detik.

Field harus diperoleh sebelumnya dan kemudian dapat digunakan kembali di perpustakaan pada string yang berbeda. Menariknya, tidak seperti kode di atas, dengan akses Field, 9% lebih cepat untuk memiliki variabel panjang akhir lokal daripada menggunakan 'chars.length' dalam pemeriksaan loop. Inilah cara akses Field dapat diatur sebagai yang tercepat:

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

Komentar khusus pada mode -server

Akses lapangan mulai menang setelah string 32 karakter dalam mode server pada mesin Java 64 bit pada mesin AMD 64 saya. Itu tidak terlihat sampai panjang 512 karakter dalam mode klien.

Juga patut dicatat saya pikir, ketika saya menjalankan JDK 8 (build 32 bit) dalam mode server, kinerja keseluruhan 7% lebih lambat untuk string besar dan kecil. Ini adalah dengan membangun 121 Desember 2013 dari rilis awal JDK 8. Jadi, untuk saat ini, tampaknya mode server 32 bit lebih lambat daripada mode klien 32 bit.

Yang sedang berkata ... sepertinya satu-satunya mode server yang layak dipanggil adalah pada mesin 64 bit. Kalau tidak, itu justru menghambat kinerja.

Untuk build 32 bit berjalan -server modepada AMD64, saya bisa mengatakan ini:

  1. String.charAt (i) adalah pemenang yang jelas secara keseluruhan. Meskipun antara ukuran 8 hingga 512 karakter ada pemenang di antara 'baru' 'digunakan kembali' dan 'bidang'.
  2. String.charAt (i) 45% lebih cepat dalam mode klien
  3. Akses lapangan dua kali lebih cepat untuk string besar dalam mode klien.

Layak juga dikatakan, String.chars () (Streaming dan versi paralel) adalah bust. Cara lebih lambat dari cara lainnya. The StreamsAPI adalah cara yang agak lambat untuk melakukan operasi string umum.

Daftar Keinginan

Java String dapat memiliki predikat menerima metode yang dioptimalkan seperti berisi (predikat), forEach (konsumen), forEachWithIndex (konsumen). Dengan demikian, tanpa perlu bagi pengguna untuk mengetahui panjang atau mengulangi panggilan ke metode String, ini bisa membantu mem-parsing perpustakaan beep-beep beepmempercepat.

Teruslah bermimpi :)

Happy Strings!

~ SH

Pengujian ini menggunakan 9 metode pengujian string berikut untuk keberadaan spasi putih:

"charAt1" - PERIKSA ISI STRING, CARA BIASA:

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" - SAMA SAJA DI ATAS TAPI MENGGUNAKAN String.length () BUKAN PEMBUATAN LOKAL FINAL int UNTUK PANJANG

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

"stream" - GUNAKANStreaming String JAVA-8 BARU DAN LULUSKAN PREDIKASI UNTUK MELAKUKAN PEMERIKSAAN

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

"streamPara" - SAMA SEKALI DI ATAS, TAPI 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();
}

"reuse" - REFILL A char REUSABLE [] DENGAN ISI STRING

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" - MENDAPATKAN SALINAN BARU DARI char [] DARI 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;
}

"new2" - SAMA SEKALI DI ATAS, TETAPI GUNAKAN "FOR-SETIAP"

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

"field1" - FANCY !! OBTAIN FIELD FOR ACCESS TO THE INTERNAL char 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);
    }
}

"field2" - SAMA SEKALI DI ATAS, TETAPI GUNAKAN "FOR-SETIAP"

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

HASIL KOMPOSIT UNTUK -clientMODE KLIEN (gabungan tes maju dan mundur)

Catatan: bahwa mode -client dengan Java 32 bit dan -server mode dengan Java 64 bit sama seperti di bawah ini pada mesin AMD64 saya.

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

HASIL KOMPOSIT UNTUK -serverMODE SERVER (gabungan tes maju dan mundur)

Catatan: ini adalah tes untuk Java 32 bit yang berjalan dalam mode server pada AMD64. Mode server untuk Java 64 bit sama dengan Java 32 bit dalam mode klien kecuali bahwa akses Field mulai menang setelah ukuran 32 karakter.

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

KODE PROGRAM YANG DAPAT DILAKUKAN

(untuk menguji pada Java 7 dan sebelumnya, hapus dua tes aliran)

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("");
    }
}
Koordinator
sumber
1
Apakah tes ini dijalankan di server JVM atau klien JVM? Optimalisasi terbaik hanya dilakukan di server JVM. Jika Anda berlari menggunakan JVM 32 bit default dan tidak ada argumen, maka Anda berlari dalam mode klien.
ceklock
2
Mendapatkan buffer dukungan bermasalah dalam kasus substring, atau string yang dibuat menggunakan String (char [], int, int), karena Anda mendapatkan seluruh buffer (setidaknya di Android), tetapi pengindeksan Anda akan berbasiskan nol. Namun, jika Anda tahu bahwa Anda tidak memiliki substring, itu akan berfungsi dengan baik.
prewett
5
Adakah ide mengapa "for (int i = 0; i <data.length (); i ++)" lebih cepat daripada mendefinisikan data.length () sebagai variabel lokal akhir?
skyin
2
Mendefinisikan suatu variabel, sama sekali, membutuhkan operasi stack dalam kode byte metode. Tetapi optimasi, dari mengenali algoritme Anda, dapat mempercepat operasi pengulangan dalam kode mesin yang sebenarnya, tanpa overhead alokasi variabel. Optimalisasi semacam itu terkadang ada dalam kompiler bytecode, terkadang tidak. Itu semua tergantung pada apakah jvm cukup pintar :-)
Koordinator
2
@DavidS angka-angka adalah tingkat (dalam nanodetik) per karakter diperiksa. Lebih kecil lebih baik.
Koordinator
14

Ini hanya optimasi mikro yang tidak perlu Anda khawatirkan.

char[] chars = str.toCharArray();

mengembalikan Anda salinan strarray karakter (dalam JDK, ia mengembalikan salinan karakter dengan menelepon System.arrayCopy).

Selain itu, str.charAt()hanya memeriksa apakah indeks memang dalam batas dan mengembalikan karakter dalam indeks array.

Yang pertama tidak membuat memori tambahan di JVM.

Buhake Sindi
sumber
Tidak menjawab pertanyaan. Pertanyaan ini tentang kinerja. Sejauh yang Anda tahu, OP mungkin telah menemukan bahwa iterasi string adalah biaya utama dalam aplikasi mereka.
rghome
9

Hanya untuk rasa ingin tahu dan untuk membandingkan dengan jawaban Saint Hill.

Jika Anda perlu memproses data berat Anda tidak boleh menggunakan JVM dalam mode klien. Mode klien tidak dibuat untuk optimisasi.

Mari kita bandingkan hasil tolok ukur @Saint Hill menggunakan JVM dalam mode Klien dan mode Server.

Core2Quad Q6600 G0 @ 2.4GHz
JavaSE 1.7.0_40

Lihat juga: Perbedaan nyata antara "java -server" dan "java -client"?


MODE KLIEN:

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) 

MODE SERVER:

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) 

KESIMPULAN:

Seperti yang Anda lihat, mode server jauh lebih cepat.

ceklock
sumber
2
Terima kasih sudah memposting. Jadi, untuk string besar, akses bidang masih 2x lebih cepat dari karakter (). Bahkan, akses lapangan menjadi lebih cepat secara keseluruhan dengan memimpin setelah 28 string panjang (gila !!) Jadi ... mode server membuat semuanya lebih cepat. Sangat menarik!
Koordinator
1
Ya, metode reflektif sangat cepat. Menarik.
ceklock
2
btw: JVM yang lebih baru secara otomatis mencari tahu dari -server atau -klien mana yang bekerja paling baik (biasanya): docs.oracle.com/javase/7/docs/technotes/guides/vm/…
jontejj
2
@jontejj dalam prakteknya tidak begitu sederhana. Jika Anda menjalankan JVM 32-bit pada Windows, maka JVM akan selalu default ke klien.
ceklock
7

Penggunaan pertama str.charAtharus lebih cepat.

Jika Anda menggali di dalam kode sumber Stringkelas, kita bisa melihatnyacharAt diimplementasikan sebagai berikut:

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

Di sini, yang dilakukannya hanyalah mengindeks array dan mengembalikan nilainya.

Sekarang, jika kita melihat implementasi dari toCharArray, kita akan menemukan di bawah ini:

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

Seperti yang Anda lihat, itu melakukan System.arraycopyyang pasti akan sedikit lebih lambat daripada tidak melakukannya.

adarshr
sumber
2
Sungguh konyol bahwa string # charAt harus melakukan pemeriksaan indeks tambahan, ketika indeks tetap diperiksa pada akses array.
Ingo
1
Dengan risiko menghidupkan kembali utas berusia 8 tahun ... Array char di belakang string mungkin lebih besar dari string itu sendiri. Artinya, jika Anda memiliki string "abcde" dan kemudian Anda menggunakan substring untuk mengekstrak "bcd" ke dalam string baru, string baru akan didukung oleh array char yang sama persis dengan string pertama. Itu sebabnya kelas string mempertahankan offset dan hitungan - jadi ia tahu karakter mana dalam array yang mewakili string ini. Jadi pemeriksaan rentang itu penting jika tidak, mungkin untuk mengakses karakter di luar ujung string ini.
dty
3

Terlepas dari jawaban @Saint Hill jika Anda mempertimbangkan kompleksitas waktu str.toCharArray () ,

yang pertama lebih cepat bahkan untuk string yang sangat besar. Anda dapat menjalankan kode di bawah ini untuk melihatnya sendiri.

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

keluaran:

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

Sepertinya niether lebih cepat atau lebih lambat

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

Untuk string panjang saya akan memilih yang pertama. Mengapa menyalin string panjang? Dokumentasi mengatakan:

public char [] toCharArray () Mengubah string ini menjadi array karakter baru.

Pengembalian: array karakter yang baru dialokasikan yang panjangnya adalah panjang dari string ini dan yang isinya diinisialisasi untuk mengandung urutan karakter yang diwakili oleh string ini.

// Edit 1

Saya telah mengubah tes untuk mengelabui optimasi JIT.

// Edit 2

Ulangi tes 10 kali untuk membiarkan JVM memanas.

// Edit 3

Kesimpulan:

Pertama-tama str.toCharArray();menyalin seluruh string dalam memori. Ini bisa memakan memori untuk string panjang. Metode String.charAt( )mencari char in char array di dalam indeks pemeriksaan kelas String sebelumnya. Sepertinya metode Strings first yang cukup pendek (yaitu chatAtmetode) sedikit lebih lambat karena pemeriksaan indeks ini. Tetapi jika String cukup panjang, menyalin seluruh array char menjadi lebih lambat, dan metode pertama lebih cepat. Semakin panjang string, semakin lambat toCharArraykinerjanya. Cobalah untuk mengubah batas dalam for(int j = 0; j < 10000; j++)lingkaran untuk melihatnya. Jika kita membiarkan kode pemanasan JVM berjalan lebih cepat, tetapi proporsinya sama.

Lagi pula itu hanya optimasi mikro.

Piotr Gwiazda
sumber
Bisakah Anda mencoba for:inopsi, hanya untuk bersenang-senang?
dasblinkenlight
2
Benchmark Anda cacat: tidak membiarkan JIT mengoptimalkannya; JIT dapat menghapus loop sepenuhnya, karena mereka tidak melakukan apa pun.
JB Nizet
String bukan na Iterableatau array.
Piotr Gwiazda
2
Ini bukan tes yang valid, Anda telah 'menghangatkan' JVM Anda dengan Test 1, yang dapat membelokkan hasilnya ke arah uji 2. Keseluruhan pertanyaan OP ini berbau optimasi mikro.
Persepsi
1
Benar. Setelah pemanasan (lihat Edit 2) kedua kali lebih kecil tetapi masih dekat satu sama lain. Dalam contoh saya tes kedua sedikit lebih cepat. Tetapi jika saya membuat String lebih lama, yang pertama lebih cepat. Semakin panjang string, tes kedua lebih lambat, karena salinan array char. Lakukan saja dengan cara pertama.
Piotr Gwiazda
2

String.toCharArray()membuat array char baru, berarti alokasi memori dengan panjang string, kemudian menyalin array char string asli menggunakan System.arraycopy()dan kemudian mengembalikan salinan ini ke pemanggil. String.charAt () mengembalikan karakter pada posisi idari salinan asli, itu sebabnya String.charAt()akan lebih cepat daripada String.toCharArray(). Meskipun, String.toCharArray()mengembalikan salinan dan bukan char dari array String asli, di mana String.charAt()mengembalikan karakter dari array char asli. Kode di bawah ini mengembalikan nilai pada indeks yang ditentukan dari string ini.

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

kode di bawah ini mengembalikan array karakter yang baru dialokasikan yang panjangnya adalah panjang dari string ini

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;
}
viki s
sumber
1

Yang kedua menyebabkan array char baru untuk dibuat, dan semua karakter dari String yang akan disalin ke array char baru ini, jadi saya kira yang pertama lebih cepat (dan kurang haus memori).

JB Nizet
sumber