ПЕРШЕ ОНОВЛЕННЯ: Перш ніж спробувати це у виробничих умовах (не рекомендується), прочитайте це спочатку: http://www.javaspecialists.eu/archive/Issue237.html
Починаючи з Java 9, описане рішення більше не працюватиме. , тому що тепер Java буде зберігати рядки як байт [] за замовчуванням.
ДРУГЕ ОНОВЛЕННЯ: Станом на 2016-10-25, на моєму AMDx64 8core та джерелі 1.8, різниці між використанням 'charAt' та доступу до поля немає. Здається, jvm достатньо оптимізований для вбудованого та впорядкування будь-яких викликів 'string.charAt (n)'.
Все залежить від тривалості String
обстежуваного. Якщо, як йдеться в запитанні, це стосується довгих рядків, найшвидший спосіб перевірити рядок - це використовувати відображення для доступу до резервної копії char[]
рядка.
Повністю рандомізований орієнтир з JDK 8 (win32 та win64) на 64-ядерному 645 AMD Phenom II 4 955 @ 3,2 ГГц (і в режимі клієнта, і в режимі сервера) з 9 різними методами (див. Нижче!) Показує, що використання String.charAt(n)
найшвидше для малих рядків, а також використання reflection
доступу до масиву резервного копіювання String для великих рядків майже вдвічі швидше.
ДОСВІД
Перевірено 9 різних методик оптимізації.
Весь вміст рядків рандомізований
Тест робиться для розмірів рядків у кратних двох, починаючи з 0,1,2,4,8,16 і т.д.
Тести робляться 1000 разів за розмір рядка
Кожен раз тести переміщуються у випадковому порядку. Іншими словами, тести робляться у випадковому порядку щоразу, коли вони виконуються, понад 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 до 256 К символів використовується швидке використання відображення для доступу до резервного масиву String. Ця методика майже вдвічі швидша, ніж String.charAt (i) (на 178% швидше). Середня швидкість за цей діапазон становила 1,111 мільярдів символів в секунду.
Поле потрібно отримати заздалегідь, і тоді воно може бути повторно використане в бібліотеці на різних рядках. Цікаво, що на відміну від наведеного вище коду, з полем доступу, на 9% швидше мати локальну змінну кінцевої довжини, ніж використовувати "chars.length" при перевірці циклу. Ось як доступ до поля можна налаштувати якнайшвидше:
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% повільнішою як для великих, так і для малих рядків. Це було під час раннього випуску JDK 8 121 грудня 2013 року. Отже, наразі здається, що 32-бітний серверний режим повільніший, ніж 32-бітний клієнтський режим.
Але, мабуть, єдиний серверний режим, на який варто звернутися, - це 64-розрядна машина. Інакше це насправді гальмує продуктивність.
Для 32-бітної збірки, що працює -server mode
на AMD64, я можу сказати так:
- String.charAt (i) - явний переможець у цілому. Хоча між розмірами від 8 до 512 символів були переможці серед «нового» «повторного використання» та «поля».
- String.charAt (i) на 45% швидше в режимі клієнта
- Доступ до поля у два рази швидший для великих рядків у режимі клієнта.
Також варто сказати, що String.chars () (Потік і паралельна версія) - це бюст. Шлях повільніше, ніж будь-який інший спосіб. TheStreams
API є досить повільним способом для виконання операцій над рядками взагалі.
Список бажань
Java String може мати предикат, який приймає оптимізовані методи, такі як містити (предикат), 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" - ТОБО ЯКЩО ПРО ВИКОРИСТАННЯ String.length () ВМЕСТИТЬ ЗРОБИТИ ЗАКОННИЙ МІСЦЕВИЙ ІНТЕРНЕТ ДЛЯ ДОВГИ
int charAtMethod2(final String data) {
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return data.length();
}
"stream" - ВИКОРИСТОВУЙТЕ НОВУ ІНТРТМЕР струну JAVA-8 і пройдіть її передбаченням, щоб здійснити перевірку
int streamMethod(final String data, final IntPredicate predicate) {
if (data.chars().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
"streamPara" - ТАКЕ ТАКОЖ, АБО ОН-ЛА-ЛА - Ідіть ПАРАЛЕЛ !!!
// 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" - Отримайте нову копію 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" - ТАКЕ ТАКЕ, ЩО ВИКОРИСТОВУЄТЬСЯ "ЗА ВСЕ"
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" - ТАКЕ ТАКЕ, ЩО ВИКОРИСТОВУЄТЬСЯ "ЗА ВСЕ"
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
(разом тести вперед та назад)
Зауважте: режим -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
РЕЖИМУ (разом випробування вперед та назад)
Примітка. Це тест на 32-бітний Java, який працює в серверному режимі на 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("");
}
}
for (char c : chars)
?