안녕하세요. 이번 시간에는 java의 replace와 replaceAll에 대해서 간단하게 짚고 넘어가도록 하겠습니다. 사실 저는 문자 c1을 문자 c2로 바꾸기 위해서 replaceAll을 주로 이용하는 편이였어요. 그런데, 아시다시피 replaceAll은 내부에서 정규식을 위한 트리를 만들어서 생각보다 느려요.

 

 모든 문자 c1을 c2로 바꾸는 메서드는 없냐? 라고 물으신다면, replace가 있어요.

 


 예제만 간단하게 보겠습니다. "maple story 205"가 있어요. 저는 공백을 '|'로 바꾸려고 해요.

 

 

 그러면 oldChar는 ' '가 되고, newChar는 '|'가 되겠죠? replace의 1번째 인자에는 ' '를, 2번째 인자에는 '|'을 넣으면 되어요.

 

 

 실행 결과는 위와 같아요. 그런데 왜 하필 특정 캐릭터를 특정 문자로 바꿀 때 왜 이걸 써야 하냐? 라고 물으신다면, 더 빨라서 그래요.  내부 소스를 간단하게 들여다 봅시다.

 

 

 val는 String 내부의 char형 배열을 의미해요. 실제 String 데이터가 저장되어 있는 배열입니다. 이 배열을 돌면서 oldChar를 만나면 break를 걸어버려요. 만약에 oldChar를 만나지 않는다면 굳이 새로운 String 객체를 생성할 필요는 없겠죠?

 

 

 oldChar가 있다면, val의 전체를 돌면서 oldChar가 있다면, buf에 newChar로 대치시켜버립니다. 2084번째 줄에 해당 부분이 있어요. 보니까, 정규 표현식을 쓰는 것도 없어 보여요. 사실 그럴 필요 조차 없습니다. 단지, c1을 c2로 대치시키는데 정규식을 쓴다는 건 너무 오버킬이기 때문입니다. 여담으로, 이 메서드에 break point를 걸어보시면, class Load와 관련된 일련의 메소드들도 보실 수 있을 겁니다.

 

 


 그런데, replaceAll은 더 느리다고 했는데요. 얼마나 느린지 직접 돌려 봅시다.

 

 

 먼저 makeList 함수는 길이가 100인 랜덤한 문자열 list를 생성합니다. 이 리스트의 길이는 n입니다.

 

 

 doReplace 메서드는 list에 있는 문자열의 첫 번째 위치에 나오는 소문자를 모두 대문자로 바꿉니다. 예를 들어 문자열이 'iotiot'라고 하면, 첫 번째로 나오는 소문자가 'i'이니, 'i'를 모두 'I'로 바꿔요. 따라서 'IotIot'가 될 겁니다. 어렵지 않죠? 리스트에 있는 문자열 모두에 대해서 해당 작업을 수행한 후에, 수행된 시간을 리턴합니다.

 

 

 doReplaceAll은 같은 작업을 replaceAll 메소드로 합니다. 어렵지 않죠?

 

 

 main 함수는 딱히 볼 게 없어요. 단지 두 메서드가 수행된 시간을 출력하기만 합니다. 결과가 어떻게 나왔을까요?

 

 

 replace를 쓴 쪽이 훨씬 빠르게 나오는 것을 알 수 있어요. 그러니까 문자열에서 문자 '#'을 문자 ' '로 치환할 때, replace를 쓰면 좋겠네요.

 

 


 이제, 문자열에 나타난 특정 문자, 예를 들어 'k'를 모두 없앤다고 해 보겠습니다. 예를 들어, 'kookrea'는 'oorea'로 바뀌어야 합니다.

 

 

 null 문자는 0이니, replace 메서드에서는 newChar를 (char)0으로 넣어버렸습니다. 4번째 줄을 보면, 패턴 "k"에 대해서 빈 문자열로 치환하게 됩니다. 출력 결과는 어떻게 될까요?

 

 

 둘 다 "oorea"로 같습니다. 그런데 실제로 같을까요?

 

 

 이 프로그램의 실행 결과는 어떻게 될까요? 둘 다 "oorea"가 출력되었으니 6번째 줄의 결과는 참일 거 같습니다.

 

 

 그런데, 사실 거짓입니다. 왜? 'k'가 (char)0, 즉 Control character로 치환되었기 때문입니다. 출력이 되지 않았을 뿐, String 내용을 저장하고 있는 배열에는 저장이 되어 있는 셈입니다. 길이는 변함이 없다는 것을 조심해야 합니다. 생각보다 문자열 관련 디버그 할 때 저런 문제들 때문에 골칫거리가 되기 때문에, 이런 문제는 잘 알아두시면 좋겠습니다.