Java/JAVA에 대하여
startsWith, endsWith 무조건 좋은가
lms0806
2024. 8. 17. 20:46
728x90
반응형
java에는 A라는 문자열의 맨앞에 B라는 문자열이 prefix(접두사)로 있는지 체크하는 함수인 startsWith가 있습니다.
또한 A라는 문자열의 맨 뒤에 B라는 문자열이 suffix(접미사)로 있는지 체크하는 함수인 endsWith가 있습니다.
여기서 하나의 궁금증이 생깁니다.
"문자열 : 문자열" 비교가 아닌 "문자열 : 문자" 비교인 경우에 startsWith나 endsWith보다 charAt()가 더 좋지 않을까?
라는 의문을 가지고 테스트에 들어갔습니다.
테스트 소스
public class Main {
static String s = "";
static int size = 1000000000;
public static void main(String[] args) {
System.out.println("JAVA Version : " + System.getProperty("java.version"));
StringBuilder sb = new StringBuilder();
for(int i = 0; i < 10000; i++) {
sb.append(1);
}
s = sb.toString();
startWith();
startChar();
endWith();
endChar();
}
public static void startWith() {
long before = System.currentTimeMillis();
for(int i = 0; i < size; i++) {
if(s.startsWith("1"));
}
long after = System.currentTimeMillis();
System.out.println("start With : " + ((after - before) / 1000.0));
}
public static void endWith() {
long before = System.currentTimeMillis();
for(int i = 0; i < size; i++) {
if(s.endsWith("1"));
}
long after = System.currentTimeMillis();
System.out.println("end With : " + ((after - before) / 1000.0));
}
public static void startChar() {
long before = System.currentTimeMillis();
for(int i = 0; i < size; i++) {
if(s.charAt(0) == '1');
}
long after = System.currentTimeMillis();
System.out.println("start char : " + ((after - before) / 1000.0));
}
public static void endChar() {
long before = System.currentTimeMillis();
for(int i = 0; i < size; i++) {
if(s.charAt(s.length() - 1) == '1');
}
long after = System.currentTimeMillis();
System.out.println("end char : " + ((after - before) / 1000.0));
}
}
결과
start With : 0.004
start char : 0.841
end With : 0.004
end char : 0.884
charAt 코드가 엄청 느리다는 것을 확인할 수 있었습니다.
'근데 저정도로 심하게 차이가 나는 이유는 머지?'라는 의구심이 들어 다른 환경에서도 테스트를 해보았습니다.
tio.run에서 테스트해본 결과
start With : 0.017
start char : 0.013
end With : 0.009
end char : 0.012
차이가 미비했습니다.
둘의 차이가 무엇일까? 라고 찾아보던 와중 제 로컬하고 tio.run의 jdk버전 차이가 발생했었습니다,.
System.out.println("JAVA Version : " + System.getProperty("java.version"));
다음과 같은 코드로 jdk버전을 확인해본 결과
- tio.run : jdk12
- 로컬 : jdk16
이였습니다.
jdk12 기준 String.java의 startsWith
public boolean startsWith(String prefix, int toffset) {
// Note: toffset might be near -1>>>1.
if (toffset < 0 || toffset > length() - prefix.length()) {
return false;
}
byte ta[] = value;
byte pa[] = prefix.value;
int po = 0;
int pc = pa.length;
if (coder() == prefix.coder()) {
int to = isLatin1() ? toffset : toffset << 1;
while (po < pc) {
if (ta[to++] != pa[po++]) {
return false;
}
}
} else {
if (isLatin1()) { // && pcoder == UTF16
return false;
}
// coder == UTF16 && pcoder == LATIN1)
while (po < pc) {
if (StringUTF16.getChar(ta, toffset++) != (pa[po++] & 0xff)) {
return false;
}
}
}
return true;
}
jdk16 기준 String.java의 startsWith
public boolean startsWith(String prefix, int toffset) {
// Note: toffset might be near -1>>>1.
if (toffset < 0 || toffset > length() - prefix.length()) {
return false;
}
byte ta[] = value;
byte pa[] = prefix.value;
int po = 0;
int pc = pa.length;
byte coder = coder();
if (coder == prefix.coder()) {
int to = (coder == LATIN1) ? toffset : toffset << 1;
while (po < pc) {
if (ta[to++] != pa[po++]) {
return false;
}
}
} else {
if (coder == LATIN1) { // && pcoder == UTF16
return false;
}
// coder == UTF16 && pcoder == LATIN1)
while (po < pc) {
if (StringUTF16.getChar(ta, toffset++) != (pa[po++] & 0xff)) {
return false;
}
}
}
return true;
}
byte coder = coder();
if (coder == prefix.coder()) {
int to = (coder == LATIN1) ? toffset : toffset << 1;
해당 부분에 차이점이 있다는 것을 발견하였습니다.
이 코드로 인하여 시간차이가 발생하지 않을까? 라는 추측을 하였습니다.
추가로 jdk8버전에서도 테스트해본 결과
start With : 0.004
start char : 0.005
end With : 0.004
end char : 0.007
다음과 같았습니다.
jdk8의 startsWith 코드는
public boolean startsWith(String prefix, int toffset) {
char ta[] = value;
int to = toffset;
char pa[] = prefix.value;
int po = 0;
int pc = prefix.value.length;
// Note: toffset might be near -1>>>1.
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
다음과 같이 매우 간단하게 동작하였습니다.
추가로 최근에 나온 jdk21의 startsWith 코드는
public boolean startsWith(String prefix, int toffset) {
// Note: toffset might be near -1>>>1.
if (toffset < 0 || toffset > length() - prefix.length()) {
return false;
}
byte[] ta = value;
byte[] pa = prefix.value;
int po = 0;
int pc = pa.length;
byte coder = coder();
if (coder == prefix.coder()) {
if (coder == UTF16) {
toffset <<= UTF16;
}
return ArraysSupport.mismatch(ta, toffset,
pa, 0, pc) < 0;
} else {
if (coder == LATIN1) { // && pcoder == UTF16
return false;
}
// coder == UTF16 && pcoder == LATIN1)
while (po < pc) {
if (StringUTF16.getChar(ta, toffset++) != (pa[po++] & 0xff)) {
return false;
}
}
}
return true;
}
다음과 같이 변경되었습니다.
jdk21에서의 테스트
start With : 0.026
start char : 0.006
end With : 0.023
end char : 0.008
결론
- startsWith와 endsWith vs charAt의 시간 차이는 버전별로 발생하는 것으로 추측된다.
- jdk12와 jdk16의 차이점인 처음에 체크하는 if문에서 시간이 오래걸리는 것으로 확인된다.
728x90
반응형