Самый быстрый способ перебирать все символы в строке
в Java, какой самый быстрый способ перебирать все символы в строке, это:
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];
}
EDIT:
что я хотел бы знать, если стоимость многократного вызова charAt
метод во время длинной итерации заканчивается тем, что меньше или больше стоимости выполнения одного вызова toCharArray
в начале, а затем непосредственно доступ к массиву во время итерация.
было бы здорово, если бы кто-то мог обеспечить надежный эталон для разных длин строк, имея в виду время прогрева JIT, время запуска JVM и т. д. и не только разница между двумя звонками в System.currentTimeMillis()
.
8 ответов
первое обновление: прежде чем вы попробуете это когда-либо в производственной среде (не рекомендуется), прочитайте это сначала:http://www.javaspecialists.eu/archive/Issue237.html Начиная с Java 9, решение, как описано, больше не будет работать, потому что теперь Java будет хранить строки как byte[] по умолчанию.
второе обновление: по состоянию на 2016-10-25, на моем AMDx64 8core и source 1.8 нет никакой разницы между использованием "charAt" и доступом к полю. Похоже, что jvm достаточно оптимизирован для inline и оптимизации любой строки.звонит чарат(Н).
все зависит от длины String
проводилась проверка. Если, как говорится в вопросе, это для долго строк, самый быстрый способ проверить строку, чтобы использовать отражение для доступа к бэк -char[]
строки.
полностью рандомизированный тест с JDK 8 (win32 и win64) на 64 AMD Phenom II 4 core 955 @ 3.2 GHZ (как в режиме клиента, так и в режиме сервера) с 9 различными техники (см. ниже!) показывает, что с помощью String.charAt(n)
является самым быстрым для небольших строк и с помощью reflection
для доступа к массиву поддержки строк почти в два раза быстрее для больших строк.
ЭКСПЕРИМЕНТ
9 различных методов оптимизации опробованы.
все содержимое строки рандомизированы
испытание сделано для размеров строки в кратных 2 начиная с 0,1,2,4,8,16 так далее.
тесты выполняются 1,000 раз на размер строки
тесты перемешиваются в случайном порядке каждый раз. Другими словами, тесты выполняются в случайном порядке каждый раз, когда они выполняются, более 1000 раз.
весь набор тестов выполняется вперед и назад, чтобы показать влияние прогрева JVM на оптимизацию и время.
весь люкс делается дважды, один раз в
-client
режим и другой в-server
режим.
выводы
- режим клиента (32 бит)
для строк от 1 до 256 символов в длину, называя string.charAt(i)
выигрывает в среднем обработка 13,4 млн. 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 длина символов, используя отражение для доступа к бэк-массив строк быстрый. эта техника почти в два раза быстрее как строку.charAt (i) (на 178% быстрее). Средняя скорость в этом диапазоне составляла 1,111 млрд символов в секунду.
поле должно быть получено заранее, а затем его можно повторно использовать в библиотеке на разных строках. Интересно, что в отличие от кода выше, с доступом к полю, на 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);
}
специальные комментарии в режиме сервера
доступ к полю начинает выигрывать после строк длиной 32 символа в режиме сервера на 64-битной Java-машине на моей машине AMD 64. Это не было видно до длины 512 символов в клиентском режиме.
также стоит отметить, я думаю, когда я был запущен JDK 8 (32-битная сборка) в режиме сервера, общая производительность была на 7% медленнее для больших и малых строк. Это было со сборкой 121 Dec 2013 JDK 8 раннего выпуска. Итак, на данный момент кажется, что 32-битный серверный режим медленнее, чем 32-битный клиентский режим.
как говорится ... кажется, единственный режим сервера, который стоит ссылаться на 64-разрядной машине. В противном случае это фактически мешает производительности.
для 32-битной сборки работает в -server mode
на AMD64, я могу сказать это:
- строку.чарат (i) является явным победителем в целом. Хотя между размерами от 8 до 512 символов были победители среди "новых ""повторного использования" и "поля".
- строку.charAt (i) на 45% быстрее в клиентском режиме
- доступ к полю в два раза быстрее для больших строк в режиме клиента.
также стоит сказать, строкой.chars () (Stream и параллельная версия) являются бюстом. Намного медленнее, чем любой другой способ. The Streams
API довольно медленный способ выполнения общих строковых операций.
Список
строка Java может иметь предикат, принимающий оптимизированные методы, такие как contains(предикат), forEach(потребитель), forEachWithIndex(потребитель). Таким образом, без необходимости пользователя знать длину или повторять вызовы методов 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" -- то же, что и выше, но используйте строку.length () вместо того, чтобы делать окончательный локальный int для длины
int charAtMethod2(final String data) {
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return data.length();
}
"stream" -- используйте IntStream новой строки JAVA-8 и передайте ей предикат для выполнения проверки
int streamMethod(final String data, final IntPredicate predicate) {
if (data.chars().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
"streamPara" -- то же, что и выше, но OH-LA - LA-GO Параллель!!!
// avoid this at all costs
int streamParallelMethod(final String data, IntPredicate predicate) {
if (data.chars().parallel().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
"повторное использование" -- REFILL многоразовый 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" -- FANCY!! Получить поле для доступа к внутреннему символу строки[]
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 бит и-серверный режим с Java 64 бит такие же, как ниже на моей машине 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
режим (вперед и назад тесты в сочетании)
Примечание: это тест для Java 32 бит работает в режиме сервера на AMD64. Режим сервера для 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 в режиме клиента. Режим клиента не предназначен для оптимизации.
давайте сравним результаты тестов @Saint Hill с использованием JVM в режиме клиента и режиме сервера.
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
что определенно будет немного медленнее, чем не делать этого.
похоже, что niether быстрее или медленнее
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;
}
для длинных строк я выберу первый. Зачем копировать длинные строки? В документах говорится:
публичный char[] toCharArray() Преобразует эту строку в новый массив символов.
возвращает: вновь выделенный символьный массив, длина которого равна длине этой строки и содержимое которого инициализируется, чтобы содержать последовательность символов, представленную этим строка.
//Edit 1
Я изменил тест, чтобы обмануть оптимизацию JIT.
//Edit 2
повторите тест 10 раз, чтобы JVM разогрелся.
//Edit 3
выводы:
в первую очередь str.toCharArray();
копирует всю строку в памяти. Это может потреблять память для длинных строк. Метод String.charAt( )
ищет char в массиве char внутри проверки класса String индекс раньше.
Похоже, для достаточно коротких строк первый метод (т. е. chatAt
способ) немного медленнее из-за этой проверки индекса. Но если строка достаточно длинная, копирование всего массива символов становится медленнее, а первый метод быстрее. Чем длиннее строка, тем медленнее toCharArray
выполняет. Попробуйте изменить лимит в for(int j = 0; j < 10000; j++)
петли, чтобы увидеть его.
Если мы позволим JVM прогревать код быстрее, но пропорции одинаковы.
в конце концов, это просто микро-оптимизации.
несмотря на ответ @Saint Hill, Если вы считаете сложность времени .toCharArray(),
первый-быстрее даже для очень больших строк. Вы можете запустить код ниже, чтобы увидеть его для себя.
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)
String.toCharArray()
создает новый массив символов, означает выделение памяти длины строки, а затем копирует исходный массив символов строки с помощью System.arraycopy()
и затем возвращает этот экземпляр абоненту.
Строка.charAt () возвращает символ в позиции i
из оригинальной копии, вот почему String.charAt()
будет быстрее, чем String.toCharArray()
.
Хотя,String.toCharArray()
возвращает копию, а не символ из исходного массива строк, где String.charAt()
возвращает символ из исходного массива.
Код ниже возвращает значение по указанному индексу строка.
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;
}
второй вызывает создание нового массива символов, и все символы из строки будут скопированы в этот новый массив символов, поэтому я бы предположил, что первый быстрее (и менее голоден по памяти).