String 클래스가 없다면
문자 배열로 문자열을 다뤄야 한다
String 클래스는 문자열을 간편하게 다룰 수 있도록 도와준다
String str1 = "hello";
String str2 = new String("hello");
String 은 클래스로 참조형으로
위 변수들에는 String 인스턴스의 참조값이 들어간다
문자열은 자주 사용되기 떄문에 "hello"
와 같이 작성하면
자바에서 묵시적으로 new String("hello")
와 같이 변경해준다
(실제로는 성능 최적화를 위해 문자열 풀을 사용)
public final class String {
private final char[] value; // 자바 9 버전 이전
private final byte[] value; // 자바 9 버전 이후
public String concat(String str) {}
public int length() {}
}
String 또한 클래스이기 때문에 위와 같은 구조를 가진다
위 클래스에서 제공하는 기능을 제외하고도
자바 자체에서 문자열에 대해 문법적으로 여러 기능을 제공한다
String 클래스의 필드는 문자 배열로 구성된다
자바에서 char 는 2byte 를 차지하지만 영어, 숫자는 보통 1byte 로 표현이 가능하다
그래서 자바 9부터는 Latin-1 인코딩의 경우 byte 배열을 사용하여 메모리를 효율적으로 사용할 수 있게 변경되었다
String 은 클래스이기 때문에 참조형 자료다
따라서 기본적으로는 문자열 + 같은 연산을 사용할 수 없다
문자열은 자주 다뤄지기 때문에 언어에서 + 연산 등을 허용한 것
String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
동등 연산자의 비교는
각 문자열의 주소값을 비교하게 되어 false
equals() 는 String 클래스에서
문자열 값을 비교하도록 오버라이딩 되어 있기 때문에 true
String a = "hello";
String b = "hello";
System.out.println(a == b); // true
System.out.println(a.equals(b)); // true
자바에서는 문자열 리터럴을 사용하는 경우
메모리 효율성과 성능 최적화를 위해 문자열 풀을 사용한다
자바가 실행되는 시점, 클레스에 문자열 리터럴이 있으면
문자열 풀에 String 인스턴스를 미리 만들어둔다
값이 같은 문자열이 있다면 새로운 인스턴스를 만들지 않는다
값이 같은 문자열은 하나의 인스턴스를 참조, 공유하여 사용하게 되는 것
Pool:
프로그래밍에서 풀은 공용 자원을 모아둔 곳을 뜻한다
여러 곳에서 사용될 수 있는 객체를 문자열 풀에 미리 만들어두고
재사용하도록 하여 성능과 메모리를 최적화 한다
자바의 문자열 풀은 힙 영역을 사용한다
문자열 풀에서 문자를 찾을 때는 해시 알고리즘을 사용한다
문자열은 참조형이므로 항상 equals() 메서드를 사용하여 동등성 비교를 해야 한다
String 은 불변 객체다
따라서 생성 이후에 내부 문자열 값을 변경할 수 없다
모든 문자열 변경은 새로운 String 인스턴스를 생성하여 반환하는 것이다
때문에 문자열 리터럴 사용 시
문자열 풀에 미리 인스턴스를 만들어 여러 변수에서 사용하게 될 때
한 쪽에 변경이 일어나더라도 다른 쪽에서는 변경이 일어나지 않는다
문자열 변경 시 불변인 String 내부 값을 변경할 수 없기 떄문에
새로운 String 객체를 생성한다
String 클래스의 단점은 이런 작업을 반복해야 할 때
제대로 사용되지 않는 인스턴스를 생성하게 되거나
인스턴스 생성 및 GC 의 반복 작업이 일어나 자원을 많이 사용하게 된다
문자열의 크기가 클수록, 더 자주 변경할수록 시스템 자원을 많이 소모한다
String builder 는 위와 같은 문제를 해결하기 위해
가변 String 을 제공한다
내부의 값을 변경하기 떄문에 새로운 객체를 생성하고, GC 로 해제하는 일이 줄어든다
가변 String 의 경우 사이드 이펙트에 주의해서 사용해야 한다
StringBuilder sb = new StringBuilder()
sb.append('a')
sb.append('b')
System.out.println(sb) // "ab"
String str = sb.toString();
StringBuilder 의 값을 조작하면
불변이 아니기 떄문에 인스턴스 필드의 값 자체가 변경된다
때문의 값을 사용하고 있는 모든 곳에서 변경이 일어난다
toString() 메서드를 사용하면 현재 값을 String 으로 반환한다
아마도 String 에서 내부 배열을 ArrayList 로 구현한 것이 아닐까 싶다
String 은 불변으로 변경 시 새로운 인스턴스 생성과 GC 작업 등에 많은 비용이 소모되므로
잦은 변경의 작업은 내부적으로 StringBuilder 를 사용해 가변적으로 이용하다가
마지막에 다시 String 으로 변경하여 사용하는 편이 좋다
자바는 문자열 리터럴의 더하기를
컴파일 시점에 실행하기 때문에 성능에 이점이 있다
문자열 변수의 더하기는 그대로 사용하면
각 변수에 어떤 문자열이 들어있는지 컴파일 시점에 알 수 없기 떄문에
String result = str1 + str2;
// 최적화
String result = new StringBuilder().append(str1).append(str2).toString();
StringBuilder 또는 StringConcatFactory 를 사용하여 최적화를 진행한다
문자열 더하기 연산 시 자바에서 최적화를 진행 해놓아
개발자가 따로 신경쓰지 않아도 된다
문자열을 루프 안에서 더하는 경우에 최적화가 이루어지지 않는다
반복문에서 문자열 연산을 해야한다면 꼭
StringBuilder 등을 반복문 외부에 두고
하나의 인스턴스에 추가하는 방법을 고려해봐야 한다
약 7~800배 정도의 시간 차이가 발생한다
StringBuilder vs StringBuffer
StringBuffer 는 StringBuilder 와 똑같은 기능을 수행하지만
내부에 동기화가 되어 있어 멀티 스레드 상황에 안전하지만
동기화 오버헤드로 인해 성능이 느리다
StringBuilder 는 동기화 처리가 되어있지 않아
멀티 스레드에는 안전하지 않지만 동기화 오버헤드가 없어 더 빠르다
StringBuilder 는 메서드 체이닝 기법을 제공한다
StringBuilder 에서 사용하는 문자열 변경 메서드는
자기 자신을 반환한다