🙂 개요
String에서 흔히 사용하는 메서드 중에서는 replaceAll라는게 있다. 다들 알다시피 replaceAll은 특정 문자를 다른 문자로 대치할 수 있게 해주는 아주 편리한 메서드이다. 그러다 replaceAll보다는 replace를 사용하는 것이 좋다는 이야기를 듣게 되어 사실인지 살펴보기 위해 해당 포스팅을 작성하게 되었다.
🤓 간단 테스트
먼저 replace에 대해 흔히 하는 오해를 바로 잡고 가야한다. replaceAll 메서드가 따로 있다보니 replace는 일치하는 첫 부분 또는 일부만 대체해 주는건가? 라고 착각할 수 있다. 예를 들어 A를 B로 대체한다고 가정할 때, AA가 BA로 대체될 거라고 착각한다. 하지만 실제로는 BB로 대체된다. (참고로 여러 일치하는 문자열 중 첫 부분만 대체하고 싶다면 replaceFirst를 사용하면 된다.)
어떤 것이 더 빠르게 동작하는지 간단한 코드를 짜고 실행시켜보았다. Supplier를 잘 모른다면 코드가 잘 이해되지 않을 수 있다. 이 코드에서는 시간 체크하는 코드의 중복을 방지하기 위해 사용했다 정도로만 이해하고, 더 궁금한 사람은 함수형 인터페이스 키워드를 더 공부해보길 바란다.
class StringTest {
@Test
void test() {
String text = "Hello World!".repeat(100);
String text2 = text;
checkTime(() -> text.replace("o", "0")); // 1. replace 실행 시간 체크
checkTime(() -> text2.replaceAll("o", "0")); // 2. replaceAll 실행 시간 체크
}
// 실행 전후로 시간을 재서 출력해주는 메서드
private void checkTime(Supplier<String> consumer) {
long before = System.currentTimeMillis();
consumer.get();
long after = System.currentTimeMillis();
System.out.println(after - before);
}
}
repeat을 통해 text의 길이를 조정하며 실행 시간에 어떤 차이점을 보이는지 테스트해보았다. 아래의 표를 보면 문자열이 길면 길수록 메서드의 실행 속도가 빨라지는 것처럼 보인다. 왜 이런 현상이 나타났는지 확인해보자.
repeat 횟수 | replace() | replaceAll() |
100 | 0 | 1 |
100,000 | 11 | 23 |
100,000,000 | 2534 | 8186 |
🧐 코드 까보기
⭐️ replace()
replace 메서드의 핵심 부분만 가져왔다. 살펴보면 문자열의 크기에 따라 반복문이 돌아가는 횟수가 다르다.
public String replace(CharSequence target, CharSequence replacement) {
String tgtStr = target.toString(); // 변경을 원하는 문자열
String replStr = replacement.toString(); // 대치할 문자열
int j = indexOf(tgtStr); // 문자열에서 tgtStr의 첫번째 위치(인덱스)를 찾아온다.
int tgtLen = tgtStr.length();
int tgtLen1 = Math.max(tgtLen, 1);
int thisLen = length();
StringBuilder sb = new StringBuilder(newLenHint);
int i = 0;
// j가 문자열의 크기보다 작고 다음 대치할 문자의 인덱스가 0보다 클 동안 반복
do {
sb.append(this, i, j).append(replStr);
i = j + tgtLen;
} while (j < thisLen && (j = indexOf(tgtStr, j + tgtLen1)) > 0);
return sb.append(this, i, thisLen).toString();
}
⭐️ replaceAll
replace에 비해 replaceAll은 굉장히 간단해보인다.
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
여기서 replaceAll이 성능에서 밀리는 이유가 하나 파악된다. Pattern이 사용되기 때문이다. Pattern 사용을 무조건 지양해야하는 것은 아니지만, 다른 훌륭한 대체 방법이 많은 상황에서 굳이 Pattern을 사용할 필요는 없어보인다.
정규표현식용 Pattern 인스턴스는, 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 된다. Pattern은 입력받은 정규표현식에 해당하는 유한 상태 머신(finite state machine)을 만들기 때문에 인스턴스 생성 비용이 높다.
ㅡ Effective Java / item 6. 불필요한 객체 생성을 피하라
🤔 무조건 replace를 사용해야 하나요?
무조건 replace를 사용해야하느냐? 라고 묻는다면 아니다. replace는 단순히 문자열을 대체할 수 있기 때문에 복잡한 정규식이 필요한 상황에서라면 적절하지 않다. 예를 들어 숫자를 문자 A로 변경해야하는 상황이라고 가정하고 테스트 코드를 작성해보았다. Pattern의 비용이 큰 것은 어쩔 수 없지만 숫자 중 어느 하나가 빠지지 않았나 체크할 필요도 없고, 코드도 훨씬 깔끔해보인다. 때와 상황에 맞게 잘 사용하라.
@Test
void parseDate() {
String text = "Hello 12345!".repeat(1000);
String text2 = text;
checkTime(() -> text.replace("0", "A")
.replace("1", "A")
.replace("2", "A")
.replace("3", "A")
.replace("4", "A")
.replace("5", "A")
.replace("6", "A")
.replace("7", "A")
.replace("8", "A")
.replace("9", "A"));
checkTime(() -> text2.replaceAll("\\d", "A"));
}
'Develop > Java' 카테고리의 다른 글
[Mockito] Invalid use of argument matchers! 에러 (0) | 2023.09.27 |
---|---|
[Java] UnaryOperator란? (0) | 2023.06.26 |
[Java] 동시성 이슈 해결하기 (1) (0) | 2022.12.09 |
[JPA] delete문이 N개가 발생한다고요?😱 (2) | 2022.11.13 |
[Java] volatile 키워드 (feat. Atomic & Concurrent) (6) | 2022.11.09 |