첫 번째 업데이트 : 프로덕션 환경에서이 작업을 시도하기 전에 (권장하지 않음) 먼저 다음을 읽으십시오. 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)
있는 가장 빠른 작은을위한입니다 문자열 및 reflection
String 백업 배열에 액세스 하는 데 사용 하는 것이 큰 문자열의 경우 거의 두 배 빠릅니다.
실험
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 mode
AMD64 에서 실행되는 32 비트 빌드의 경우 다음과 같이 말할 수 있습니다.
- String.charAt (i)는 전체적으로 확실한 승자입니다. 크기는 8-512 자 사이이지만 '신규' '재사용'및 '필드'에서 승자가있었습니다.
- 클라이언트 모드에서 String.charAt (i)가 45 % 빠릅니다.
- 클라이언트 모드에서 큰 문자열의 경우 필드 액세스가 두 배 빠릅니다.
또한 String.chars () (스트림 및 병렬 버전)는 흉상입니다. 다른 방법보다 속도가 느립니다. Streams
API는 일반 문자열 연산을 수행하는 다소 느린 방법입니다.
위시리스트
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("");
}
}
for (char c : chars)
이야?