자바에서는 대표적으로 문자열을 다루는 자료형 클래스로 String, StringBuffer, StringBuilder 라는 3가지 자료형을 지원한다.
위 3가지 클래스 자료형은 모두 문자열을 다루는데 있어 공통적으로 사용되지만, 사용 목적에 따라 쓰임새가 많이 달라지게 된다.
1. String
String - immutable class(불변 클래스)
- + 연산이나 concat() 메소드로 문자열을 이어붙일수 있다.
- 문자열 간 + 연산이 있는 경우 자바에서 자동으로 새로운 String 객체(인스턴스)를 생성한다, 따라서 문자열을 많이 결합하면 결합할수록 공간의 낭비뿐만 아니라 속도 또한 매우 느려지게 된다는 단점이 있다
- "Hello" , " " , "World" 총 3개의 String 자료형 객체가 만들어진 것이다.
String result = "";
result += "hello";
result += " ";
result += "jump to java";
System.out.println(result); // hello jump to java
// → 심플하지만 연산 속도가 느리다는 단점이 있다
String 자료형은 한번 값이 생성되면 그 값을 변경할 수 없다.
이러한 속성의 클래스를 immutable class (불변 클래스) 라고한다.
불편 클래스는 append(), insert() 와 같은 값을 변경하는 set 메소드가 없다.
# 불편한 것 같은데 왜 사용하는가?
캐싱, 보안, 동기화, 성능측면 이점
1. 캐싱 : String을 불변하게 함으로써 String pool에 각 리터럴 문자열의 하나만 저장하며 다시 사용하거나 캐싱에 이용가능하며 이로 인해 힙 공간을 절약할 수 있다는 장점이 있다.
2. 보안 : 예를 들어 데이터베이스 사용자 이름, 암호는 데이터베이스 연결을 수신하기 위해 문자열로 전달되는데, 만일 번지수의 문자열 값이 변경이 가능하다면 해커가 참조 값을 변경하여 애플리케이션에 보안 문제를 일으킬 수 있다.
3. 동기화 : 불변함으로써 동시에 실행되는 여러 스레드에서 안정적이게 공유가 가능하다.
멀티 스레드 환경에서 객체가 변화되는 상황이라면 불변 클래스 인스턴스를 사용하는 것이 좀 더 신뢰할 수 있기 때문이다.
하나의 객체에 접근하면서 각각 객체가 서로 영향을 주어서는 안되는 경우 불변 클래스 인스턴스를 사용하면 값이 변하지 않는다는 점이 보장된다.
2. StringBuffer / StringBuilder 클래스
StringBuffer/ StringBuilder - mutable class(가변 클래스)
StringBuffer / StringBuilder 클래스는 문자열을 연산(추가하거나 변경) 할 때 주로 사용하는 자료형이다.
2.1 String Buffer
- StringBuffer는 단 한번만 생성된다.
- StringBuffer인 sb 라는 객체에 append() 메소드를 사용하여 문자가 추가 되는 것이다.
- insert() 메소드로 원하는 위치에 문자열을 삽입할 수 있다.
- 생성된 객체가 변경이 가능하다는 점에서 mutable class (가변 클래스) 라고한다.
StringBuffer sb = new StringBuffer(); // StringBuffer 객체 sb 생성
sb.append("hello");
sb.append(" ");
sb.append("jump to java");
String result = sb.toString();
System.out.println(result); // hello jump to java
// → + 연산보다는 복잡해 보이지만 연산 속도가 빠르다는 장점이 있다
StringBuffer 클래스는 내부적으로 buffer 라고 하는 독립적인 공간을 가진다.
버퍼 크기의 기본값은 16개의 문자를 저장할 수 있는 크기이며, 생성자를 통해 그 크기를 별도로 설정할 수도 있다.
만일 문자열 연산중에 할당된 버퍼의 크기를 넘게 되면 자동으로 버퍼를 증강 시킨다 다만, 효율이 떨어질 수 있으므로 버퍼의 크기는 넉넉하게 잡는 것이 좋다.
StringBuffer 를 사용하면 문자열을 바로 추가할 수 있으므로, 공간의 낭비도 없으며 연산 속도도 매우 빨라진다.
StringBuffer sb = new StringBuffer(); // 기본 16 버퍼 크기로 생성
// sb.capacity() - StringBuffer 변수의 배열 용량의 크기 반환
System.out.println(sb.capacity()); // 16
sb.append("1111111111111111111111111111111111111111"); // 40길이의 문자열을 append
System.out.println(sb.capacity()); // 40 (추가된 문자열 길이만큼 늘어남)
# 장점이 많은 것 같은데 StringBuffer ,String Builder 만 사용하면 안되나?
상황에 따라 다르다. StringBuffer 는 String 보다 무거운 편에 속한다.
문자열 추가나 변경등의 작업이 많은 경우 StringBuffer 를 사용하고,
변경 작업이 거의 없는 경우 String 을 사용하는 것이 좋다.
# String Buffer 내장 메소드
String str = "abcdefg";
StringBuffer sb = new StringBuffer(str); // String -> StringBuffer
System.out.println("처음 상태 : " + sb); // 처음상태 : abcdefg
System.out.println("문자열 String 변환 : " + sb.toString()); // StringBuffer를 String으로 변환하기
System.out.println("문자열 추출 : " + sb.substring(2,4)); // 문자열 추출하기
System.out.println("문자열 추가 : " + sb.insert(2,"추가")); // 문자열 추가하기
System.out.println("문자열 삭제 : " + sb.delete(2,4)); // 문자열 삭제하기
System.out.println("문자열 연결 : " + sb.append("hijk")); // 문자열 붙이기
System.out.println("문자열의 길이 : " + sb.length()); // 문자열의 길이구하기
System.out.println("용량의 크기 : " + sb.capacity()); // 용량의 크기 구하기
System.out.println("문자열 역순 변경 : " + sb.reverse()); // 문자열 뒤집기
System.out.println("마지막 상태 : " + sb); // 마지막상태 : kjihgfedcba
Tip
StringBuilder 나 StringBuffer 클래스의 사용 문법은 동일하다.
메서드 | 설 명 |
StringBuffer( ) | 버퍼의 길이를 지정하지 않으면 크기가 16 인 버퍼를 생성 |
StringBuffer(int length) | length 길이를 가진 StringBuffer 클래스의 인스턴스(buffer)를 생성 |
StringBuffer(String str) | 지정한 문자열( str )의 길이보다 16 만큼 더 큰 버퍼를 생성 |
StringBuffer append(boolean b) StringBuffer append(char c) StringBuffer append(char[ ] str) StringBuffer append(double d) StringBuffer append(float f) StringBuffer append(int i) StringBuffer append(long l) StringBuffer append(Object obj) StringBuffer append(String str) |
매개변수로 입력된 값을 문자열로 변환하여 StringBuffer 인스턴스가 저장하고 있는 문자열의 뒤에 덧붙임 |
int capacity( ) | StringBuffer 인스턴스의 버퍼크기 반환 (자료형의 할당된 크기를 반환) |
int length( ) | StringBuffer 인스턴스에 저장된 문자열의 길이 반환 (버퍼에 담긴 문자열 데이터를 반환) |
char charAt(int index) | 지정된 위치( index )에 있는 문자를 반환 |
StringBuffer delete(int start, int end) | 시작위치( start )부터 끝 위치( end )사이에 있는 문자를 제거 단, end 위치의 문자는 제외(start <= x < end) |
StringBuffer deleteCharAt(int index) | 지정된 위치( index )의 문자를 제거 |
StringBuffer insert(int pos, boolean b) StringBuffer insert(int pos, char c) StringBuffer insert(int pos, char[ ] str) StringBuffer insert(int pos, doule d) StringBuffer insert(int pos, float f) StringBuffer insert(int pos, int i) StringBuffer insert(int pos, long l) StringBuffer insert(int pos, Object obj) StringBuffer insert(int pos, String str) |
두 번째 매개변수로 받은 값을 문자열로 변환하여 지정된 위치( pos )에 추가 ( pos 는 0부터 시작) |
StringBuffer replace(int start, int end, String str) | 지정된 범위( start ~ end )의 문자들을 주어진 문자열로 바꾼다. 단, end 의 위치는 범위에 포함되지 X (start <= x < end) |
StringBuffer reverse( ) | StringBuffer 인스턴스에 저장되어 있는 문자열의 순서를 거꾸로 나열 |
void setCharAt(int index, char ch) | 지정된 위치( index )의 문자를 주어진 문자( ch )로 바꾼다. |
void setLength(int newLength) | 지정된 길이로 문자열의 길이를 변경 길이를 늘리는 경우에는 나머지 빈공간들을 널문자( \u0000 )로 채운다. |
String toString( ) | StringBuffer 인스턴스의 문자열을 String 으로 변환 |
String substring(int start) String substring(int start, int end) |
지정된 범위 내의 문자열을 String 으로 뽑아서 반환 (시작위치( start )만 지정하면 시작위치부터 끝까지 뽑아서 반환) |
2.2 String Builder
StringBuffer와 비슷한 자료형으로 StringBuilder 자료형이 있다. StringBuilder 사용법은 StringBuffer와 동일하다.
3. String vs (StringBuffer, StringBuilder) 비교
문자열 자료형의 불변과 가변
3.1 String은 불변
기본적으로 자바에서는 String 객체의 값은 변경할 수 없다.
이는 한번 할당된 공간이 변하지 않는다고 해서 '불변(immutable)' 자료형 이라고 불리운다. 그래서 초기공간과 다른 값에 대한 연산에서 많은 시간과 자원을 사용하게 된다는 특징이 있다.
실제로 String 객체의 내부 구성 요소를 보면 다음과 같이 되어 있다.
인스턴스 생성 시 생성자의 매개변수로 입력받는 문자열은 이 value 라는 인스턴스 변수에 문자형 배열로 저장되게 된다. 이 value 라는 변수는 상수(final)형이니 값을 바꾸지 못하는 것이다.
public final class String implements java.io.Serializable, Comparable {
private final byte[] value;
}
Tip
jdk 8 까지는 String 객체의 값은 char[] 배열로 구성되어져 있지만, jdk 9부터 기존 char[]에서 byte[]을 사용하여 String Compacting을 통한 성능 및 heap 공간 효율(2byte -> 1byte)을 높이도록 수정되었다.
아래 예제 코드를 보면 변수 str 이 참조하는 메모리의 "Hello" 라는 값에 "World" 라는 문자열을 더해서 String 객체의 자체의 값을 업데이트 시킨 것으로 보일수 있다.
하지만 실제로는 메모리에 새로 "Hello World" 값을 저장한 영역을 따로 만들고 변수 str 를 다시 참조하는 식으로 작동한다.
String str = "hello";
str = str + " world";
System.out.println(str); // hello world
이외에도 문자열을 다루는데 있어 가장 많이 사용하는 trim 이나 toUpperCase 메소드 사용 형태를 보면, 문자열이 변경되는 것 처럼 생각 될 수도 있지만 해당 메소드 수행 시 또 다른 String 객체를 생성하여 리턴할 뿐이다.
String sql = "abc"; // "abc"
sql.toUpperCase(); // "ABC"
System.out.println(sql); // "abc" - toUpperCase를 해도 자체 문자열은 변경되지 않는다 (불변)
3.2 StringBuffer / StringBuilder 는 가변
StringBuffer나 StringBuilder의 경우 문자열 데이터를 다룬다는 점에서 String 객체와 같지만, 객체의 공간이 부족해지는 경우 버퍼의 크기를 유연하게 늘려주어 가변(mutable)적이라는 차이점이 있다.
두 클래스는 내부 Buffer(데이터를 임시로 저장하는 메모리)에 문자열을 저장해두고 그 안에서 추가, 수정, 삭제 작업을 할 수 있도록 설계되어 있다.
String 객체는 한번 생성되면 불변적인 특징 때문에 값을 업데이트하면, 매 연산 시마다 새로운 문자열을 가진 String 인스턴스가 생성되어 메모리공간을 차지하게 되지만,
StringBuffer / StringBuilder 는 가변성 가지기 때문에 .append() .delete() 등의 API를 이용하여 동일 객체내에서 문자열 크기를 변경하는 것이 가능하다.
따라서 값이 변경될 때마다 새롭게 객체를 만드는 String 보다 훨씬 빠르기 때문에, 문자열의 추가, 수정, 삭제가 빈번하게 발생할 경우라면 String 클래스가 아닌 StringBuffer / StringBuilder를 사용하는 것이 이상적이라 말할 수 있다.
Tip
StringBuilder 나 StringBuffer 클래스의 사용 문법은 둘이 똑같다.
StringBuffer의 내부구조를 보면 상수(final) 키워드가 없는것을 볼 수 있다.
public final class StringBuffer implements java.io.Serializable {
private byte[] value;
}
아래 코드는 별* 문자를 루프문을 순회할때마다 추가해 길다란 별 문자열을 만드는 예제로, 각각 String 객체와 StringBuffer 객체를 이용했을시 차이를 보여준다.
String star = "*";
for ( int i = 1; i < 10; i++ ) {
star += "*";
}
StringBuffer sb= new StringBuffer("*");
sb.append("*********");
String 객체일 경우 매번 별 문자열이 업데이트 될때마다 계속해서 메모리 블럭이 추가되게 되고, 일회용으로 사용된 이 메모리들은 후에 Garbage Collector(GC)의 제거 대상이 되어 빈번하게 Minor GC를 일으켜 Full GC(Major Gc)를 일으킬수 있는 원인이 된다.
반면 StringBuffer는 위 사진 처럼 자체 메모리 블럭에서 늘이고 줄이고를 할수 있기 때문에 훨씬더 효율적으로 문자열 데이터를 다룰 수 있다는 것을 볼 수 있다.
연관된 글:
[Java] Java의 동작 원리 - Garbage Collection
참고 :
[JAVA] ☕ String / StringBuffer / StringBuilder 차이점 & 성능 비교
https://jroomstudio.tistory.com/2
'개발 > Java' 카테고리의 다른 글
[Java] 접근 제어자 (Access Modifier)와 캡슐화(encapsulation) (0) | 2023.03.16 |
---|---|
[Java] Enum (0) | 2023.02.23 |
[JAVA] 정규표현식 (0) | 2023.02.21 |
batch (0) | 2023.02.19 |
[JAVA] 자바(Java) 버전별 특징 (0) | 2023.02.09 |