프로그래밍을 하다보면 문자열을 조합하여 새로운 문자열을 만드는 것은 매우 자주 있는 일입니다.
JAVA에서는 기본적으로 +, StringBuilder, StringBuffer 를 제공하며 이 차이점을 묻는 질문이 면접 질문으로 자주 나옵니다.
(String 클래스에서 제공하는 format, join 등의 메소드를 통해서도 할 수 있습니다.)
저는 개인적으로 최근까지 + 연산에 대해 큰 오해를 하고 있었고 이에 대해 알아가는 과정에서 몇가지 추가적인 것들을 알 수 있었습니다.
1. JAVA 1.5 이후부터 + 연산은 Compiler 가 StringBuilder 또는 StringBuffer로 바꾸어 처리한다.
처음 프로그래밍을 배울 당시 학원에서 강사님이 + 연산은 새로운 String 인스턴스를 만들기 때문에 메모리 적으로 매우 비효율적이라고 이야기를 하셨습니다.
그리고 첫 회사에 입사해 업무 시에 + 연산을 사용해도 되지만 StringBuilder를 사용하는 코드가 많았기 때문에 그 이야기가 맞았구나라고 생각을 하고 있었습니다.
그런데 이직을 하고 코드를 보면서 + 연산이 자주 사용되는 것을 보고 뭔가 내가 모르는 것이 있구나 싶어 알아보았을 때 잘못 알고 있다는 것을 알게 되었습니다.
1.5부터는 + 연산의 문제점 개선을 위해 컴파일 시점에 +를 StringBuilder(or StringBuffer)로 변경하도록 해 많은 경우에 + 연산관 StringBuilder(or StringBuffer)는 동일한 동작을 하는 것이었습니다.
글의 가독성을 위해 아래 부분부터는 StringBuilder(or StringBuffer)로 쓰지 않고StringBuilder로만 지칭하겠습니다.
오라클이 제공하는 JAVA 스펙 문서에 1.5 버전을 제공하고 있지 않기 때문에 정확히 발췌하지 못했지만 제가 참고했던 여러 블로그(글 하단에 첨부)에서 일관된 내용을 확인할 수 있습니다.
2. 하지만 + 연산과 직접 StringBuilder를 사용하는 것은 다르게 동작한다.
사실 하마터면 + 연산은 StringBuilder와 동일하게 동작하는구나 하고 넘어갈 뻔 했습니다.
그런데 참 감사하게도 둘의 동작이 다른 부분이 있다는 것을 회사 동료분께서 찾아보고 공유해주셨습니다.
2가지 측면에서
첫번째는 StringBuilder, StringBuffer는 기본 생성자가 내부 capacity를 16byte로 초기화하는데 이를 조정해줄 수 없다는 것입니다.
append 시에 capacity가 부족한 경우 capacity를 늘리는 동작(더 긴 배열을 만들고 기존 배열 복사)이 일어납니다. 16byte보다 긴 문자열을 만들기 위해 + 연산을 사용하면 직접 적절한 capacity를 지정해 StringBuilder를 생성해 사용하는 것보다 내부적으로 capacity를 늘리기 위해 배열을 복사하는 추가 비용을 발생합니다.
두번째는 JAVA 8까지는 반복문(loop)에서 + 연산은 매 loop마다 새로운 StringBuilder(or StringBuffer) 객체를 만든다는 것입니다.
그렇기 때문에 반복문을 통해 문자열을 더하는 경우에는 직접 StringBuilder를 사용하는 것에 비해 +연산을 사용하는 경우가 StringBuilder 객체 생성을 (loop 횟수 - 1) 번 더 하게 되는 것입니다.
다만 JAVA9 부터는 이러한 문제점을 해결하기 위해서 StringConcatFactory가 도입되어 관련 내용이 많이 개선되었다고 합니다.
StringConcatFactory 관련해서는 아직 내용 파악이 정확히 되지 않았는데, 기회가 되면 파악한 후에 글을 한번 써보겠습니다.
3. JAVA 9와 JAVA 11에 문자열 관련 처리 개선이 있었다.
회사 팀장님이 추가로 공유해주신 내용은
JAVA 9부터 영문은 1byte로 줄어서 메모리 최적화가 되었고
JAVA 11의 String function들을 사용하면 직접 문자열 조작하는 것보다 더 최적화된다고 합니다. (아마도 String.repeat, String.lines 등)
그렇기 때문에 문자열을 많이 다루는 서비스는 아무래도 JAVA 11 이상을 사용하면 좋다고 합니다.
JAVA9부터 영문은 1byte로 줄어든 것과 관련해서는 JAVA9 Compact String을 검색하시면 많은 자료를 찾아볼 수 있고 참고 자료에도 관련 글을 넣어 두었습니다.
간단히 설명하면 굳이 2byte로 할 필요가 없는 것을 1byte로 저장하는 것은 JAVA 6에 Compressed String라는 이름으로 도입 되었었습니다. 하지만 문자열 연산들은 주로 2byte를 가정하고 만들어져 있었고 이로 인해 unwrap하는 과정이 빈번하게 일어나 의도하지 않은 성능 이슈를 가져오는 경우가 생겼고 JAVA7에서 제거 되었었습니다. 이를 String 클래스 내부 구조와 컴파일러 개선등을 통해(동일한 목표를 다른 방법을 통해) JAVA9에서 다시 도입한 것입니다.
4. Concatenation vs Formatter
C언어에는 printf가 있고 Java에는 String.format 과 같은 문자열 formatter가 있습니다.
회사 동료 중 한 분이 출력에 Formatter를 쓰는 것이 보안과 정적 타입 체크 면에서 더 낫다고 이야기 하셨다.
출력과 문자열 조합은 또 별개의 문제이니 약간 다른 문제이기는 하지만 JAVA의 문자열 조합에
String.format과 Concatenation(+, StringBuilder, StringBuffer) 중 어떤 것을 써야 할까?
너무 당연한 이야기이지만 두 방법에는 장단점이 있기 때문에 그것을 알고 각 상황에 맞게 사용하면 될 것입니다.
String.format이 적절할 수 있는 상황
1. format에 사용되는 인자의 수가 적어 가독성을 높일 수 있는 경우
2. 문자열 현지화(Localization)가 필요한 경우
3. double을 자리수에 맞추어 출력하는 것 등 Concatenation 만으로는 쉽게 문자열 조합하기 어려운 경우
Concatenation이 적절할 수 있는 상황
1. 문자열 연결에 사용되는 변수가 많아 format에 들어가는 인자의 수가 많아지고 이로 인해 버그의 위험이 높은 경우.
String.format은 인자의 개수나 타입이 잘못된 경우 컴파일 시점이 아닌 런타임에 예외 발생합니다.
2. 문자열 현지화가 필요없고 속도가 중요한 경우.
여러 글에서 모두 String.format은 Concatenation에 비해 느린 것으로 이야기 하며 아래 참고글 중 하나에서는 5~30배 정도 느린 것으로 이야기 합니다.
참고 글
https://jinseongsoft.tistory.com/369
[Java] String '+'문자열 연결 연산은 내부에서 어떻게 이루어질까?
Java 문자열 연결 연산 동작 메커니즘 Java String을 다루다 보면 문자열 조합을 위해서 + 연산자를 사용하는 경우가 많습니다. 아래 예제를 실행하게 되면 내부에서는 어떤 동작이 이루어질까요? pub
jinseongsoft.tistory.com
https://jerry92k.tistory.com/50
[Java] String + 연산 최적화
JDK 5 이전 버전에서는 String + 연산시, 매 연산마다 String 객체가 생성되는 비효율이 있었습니다. String str1 = "a"; String str2 = str1+"b"+"c"; // => str1 객체 생성, str1+"b" 객체 생성, str1+"b"+"c"..
jerry92k.tistory.com
https://www.baeldung.com/java-9-compact-string
https://www.invicti.com/blog/web-security/string-concatenation-format-string-vulnerabilities/
The Problem of String Concatenation and Format String Vulnerabilities
This blog post explains the basics of string concatenation, the problems it causes and insecure string concatenation functions in C. It then examines format string vulnerabilities, how they appear in different web applications, and their relation to XSS vu
www.invicti.com
Is it better practice to use String.format over string Concatenation in Java?
Is there a perceptible difference between using String.format and String concatenation in Java? I tend to use String.format but occasionally will slip and use a concatenation. I was wondering if o...
stackoverflow.com
'제안&정리' 카테고리의 다른 글
리팩토링은 점진적으로 그리고 테스트 작성부터 (0) | 2022.05.01 |
---|---|
테스트 시 API 조회에 목사용? SpringBootTest & MockBean? (0) | 2022.04.10 |
[테스트 코드 작성기 1] 간단한 통합 테스트 작성 (0) | 2022.03.21 |
Log 잘 남기기 (0) | 2022.01.16 |
MongoDB Embeded vs Reference (0) | 2022.01.11 |