[Java] String의 더하기 연산으로 인한 문제점과 StringBuffer, StringBuilder의 특성과 차이점에 대해서
자바[Java] 2019. 6. 22. 00:30다음글
String의 불변성으로 인한 문자열 append 연산 문제점
최근에는 Java의 컴파일러의 최적화가 진행됨에 따라 문자열의 + 연산시의 문제가 많이 해결되었습니다만 과거에는 String의 불변성에 따른 문제점이 있습니다.
JDBC API를 이용할때 다음과 같이 SQL을 + 연산하여 여러 문자열 리터럴을 더해 사용합니다. 여기서는 아주 간단한 sql을 작성해 보았지만 회계관련 ERP 시스템과의 연동을 하는 경우에는 쿼리는 수십 ~ 수백줄이 되기도 합니다. 조건으로 사용하는 컬럼이 늘어날수록 + 연산은 그만큼 증가합니다.
String tableName = "user_tbl";
String sql = "SELECT * FROM " + tableName + " WHERE " + "username = '" + userName + "' AND userId = " + id; |
cs |
언뜻 보기에는 한 줄로 작성된 SQL이기에 하나의 문자열 객체만 생성될것으로 예상하기 쉽지만 실제로는 "SELECT * FROM ", tableName, " WHERE ", "username = '" 등의 값을 가진 여러개의 문자열이 생성되고 최종적으로 이 값들을 모두 더한 문자열 객체가 생성됩니다. 즉 heap 메모리상에 많은 문자열 객체가 생성되는것입니다.
또한 + 연산이 수행될때마다 새로문 문자열 객체가 생성되므로 위 예제 코드 한문장에서 생성되는 문자열객체만 해도 어마어마(?)합니다. 문자열이 가변객체였다만 String 객체 하나에 내부적으로 갖는 값만 계속 변경되는 식이었겠지만 불변성을 지닌 객체이기에 이런 일이 발생하는것입니다.
최근에는 JPA나 mybatis 등의 DB와 연동시 사용하는 여러가지 ORM 프레임워크들이 있기에 쿼리 문자열을 직접 연산하는 경우가 적어졌지만 과거에는 서버 사양도 낮았고 JDBC API를 다이렉트로 사용했기 때문에 sql 작성시 String 연산을 많이 사용함에 따라 발생되는 메모리 관련 이슈들이 많았다고 합니다.
물론 자바 컴파일러의 발전으로 인해 JDK1.5부터는 불필요한 + 연산의 경우에는 컴파일 과정에서 하나의 문자열로 합쳐지게 됩니다.
String userName = "kim";
int id = 30;
String sql = "SELECT * FROM " + "user_tbl" + " WHERE " + "username = '" + userName + "' AND userId = " + id; |
cs |
이 위의 코드는 컴파시 아래와 같은 동작을 하도록 바이트코드로 컴파일됩니다.
String userName = "kim";
int id = 30;
String sql = "SELECT * FROM user_tbl WHERE username = '" + userName + "' AND userId = " + id; |
cs |
StringBuffer, StringBuilder클래스
지금까지 String의 특성을 설명했는데요. 문자열 사이에 + 연산을 하면서 새로운 String 객체가 생성되는 낭비를 방지하고자 StringBuffer, StringBuilder 두가지의 클래스가 등장했습니다.
StringBuffer, StringBuilder 두 개의 클래스는 자신이 가지고 있는 문자열값의 추가, 변경, 삭제 가 가능하다는 것입니다. 둘의 세부적인 차이는 나중에 설명하기로하고 이 둘이 공통으로 가지고 있는 기능은 대략 다음과 같습니다.
//StringBuffer sb = new StringBuffer(); //빌더나 버퍼 둘 중 아무거나 사용 가능
StringBuilder sb = new StringBuilder();
sb.append("SELECT * FROM ");
sb.append("user_tbl");
sb.append(" WHERE");
sb.append("username = '");
sb.append("kim");
sb.append("' AND userId = ");
sb.append(30);
System.out.println(sb.toString()); |
cs |
StringBuffer, StringBuilder는 생성자로 생성 후 append() 메서드를 통해 내부적으로 가지고 있는 문자열 값을 가변적으로 추가할 수 있습니다. 물론 delete()나 setCharAt() 메서드를 통해 특정 위치의 문자열을 치환 또는 제거할수도 있습니다.
각각의 메서드는 매개변수의 타입별로 오버로딩 되어있기 때문에 문자열 뿐만 아니라 정수나 실수 객체타입의 경우 toString()을 수행하여 내부적으로 연산을 수행합니다.
String과 이 둘의 차이점은 문자열 연산으로 인해 나온 결과물이 새로운 객체로 생성되는것이 아니라는 점입니다. 만약 아래와 같이 수행했을때를 가정해보죠.
StringBuilder sb = new StringBuilder();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d"); |
cs |
String str = "a" + "b" + "c" + "d"; |
cs |
String 객체만을 사용했을때와 빌더, 버퍼를 이용했을때의 차이점은 아래와 같습니다. 문자열 연산을 여러번 자주 수행할수록 이 둘의 차이점은 극명할것입니다.
StringBuffer, StringBuilder 사용시 주의할 점
StringBuffer, StringBuilder이 두 클래스의 특성을 알았으니 앞으로는 이 둘을 사용하게 되는 일이 있을것입니다. 그러나 여기서 주의해야 할 점이 있습니다. 다음과 같이 이 두개의 클래스를 사용하는 경우 무의미해진다는것을 아셔야 합니다.
StringBuilder sb = new StringBuilder();
sb.append("a" + "b");
sb.append("c" + "d"); |
cs |
StringBuilder를 사용하는것까지는 좋았으나 append() 메서드의 매개변수로 넘기기 전에 String 의 + 연산을 수행하고 있습니다. 이렇게 되면 StringBuilder를 사용하는 의미가 많이 사라집니다.
StringBuffer, StringBuilder의 차이점
StringBuffer, StringBuilder는 사용법은 거의 동일하나 가장 명확한 차이점이 있습니다. 그것은 Thread에 안전하냐 아니냐 입니다.
-
StringBuffer - 스레드에 안전함
-
StringBuilder - 스레드에 안전을 보장하지 않음
즉 특정 메서드 내부에서 생성하고 연산하는 경우에는 StringBuilder를 사용해도 관계없습니다. 메서드 내부에 생성되는 변수는 스택공간에 생성되기에 여러 스레드 사이에서 공유될 수 없기 때문입니다.
public void someMethod(){
StringBuilder sb = new StringBuilder(); //스레드간에 공유될 일이 없으므로 이경우 사용 가능
sb.append("a");
..doSomething..
} |
cs |
그러나 웹어플리케이션 환경등과 같이 여러 스레드 사이에 공유되어 사용되는 경우에는 StringBuffer를 사용해야 스레드에 안전합니다. someMethod()를 여러 스레드에서 동시에 호출하는 경우 StringBuilder는 정상적인 결과를 보장할 수 없습니다.
public class UserService {
StringBuffer sb = new StringBuffer();
public void someMethod(String value){
sb.append(value);
..doSomething..
}
} |
cs |
StringBuilder의 append() 메서드
public StringBuilder append(String str) {
super.append(str);
return this;
} |
cs |
StringBuffer의 append() 메서드
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
} |
cs |
'자바[Java]' 카테고리의 다른 글
[Effective Java] 문자열 String 영어 대소문자 무시하여 비교시에는 equalsIgnoreCase() 메서드를 사용하자. (0) | 2019.07.11 |
---|---|
[JAVA] Java I/O(입출력) [2] 자바 입출력 패키지(java.io) 구조 (1) | 2019.07.11 |
[Java] String의 불변성(Immutable)과 그 이유 (1) | 2019.06.21 |
[Logback] 로그백(logback) 스프링(Spring)에서 사용 하는 방법 (1) | 2019.04.04 |
[Logback] 로그백(logback) 다운로드 및 사용해보기 (0) | 2019.04.03 |