앞선 포스팅에서 java.lang.String 클래스에 대한 내용을 설명하면서 String 클래스는 Immutable, 변수가 참조하는 메모리 주소에 저장된 값을 변경할 수 없다는 특성에 대해 설명했었다. 이러한 특성 때문에 String 인스턴스의 경우, "+"로 문자열을 합산하는 연산작업이나 변경 작업이 빈번해지는 경우, 메모리 공간 소모율이 증가할 수 밖에 없다. 따라서 Java에서는 변수에 할당된 값, 즉 메모리에 적재된 문자열 값도 메모리에서 바로 변경할 수 있는 StringBuffer라는 클래스를 별도로 제공한다. 변수가 참조하는 메모리 주소에 저장된 값을 변경할 수 있기 때문에 StringBuffer는 mutable(변이, 변경이 가능)한 특성을 가진다.
StringBuffer 외에도 멀티스레드에 취약한 StringBuffer를 보완하기 위한 StringBuilder 클래스도 존재한다. 추후에 멀티스레드에 대한 포스팅을 하게 될 때 다시 언급하겠지만 StringBuilder는 StringBuffer와 동일하게 사용하면 되기에 이번 포스팅에서는 잠깐만 언급하고 넘어가려 한다. 대신 짧아진 포스팅 내용만큼 문자열의 인코딩 변환에 대해 포스팅을 추가해보려한다.
1. StringBuffer 클래스
StringBuffer 클래스는 String의 단점일 수 있는 특성을 보완한 클래스이기 때문에 인스턴스 생성으로 문자열을 참조변수에 저장하는 방식은 String 클래스와 매우 유사하다. 필자가 "동일하다"가 아닌 "매우 유사하다"라는 문장으로 끝맺음을 한 이유는, String과 달리 StringBuffer는 문자열 리터럴 값을 바로 참조변수에 선언하거나 문자 배열을 생성자 매개인자로 사용할 수 없기 때문이다.
StringBuffer의 생성자는 크게 세 가지가 있다.
---------------------------------------------------------------------------------------------------
StringBuffer() - 크기 16인 Char 배열이 메모리에 할당됨.
StringBuffer(int index) - 크기 index인 char 배열이 메모리에 할당됨.
StringBuffer(String str) - 문자열 길이만큼의 char 배열이 메모리에 할당됨.
---------------------------------------------------------------------------------------------------
StringBuffer은 String과 동일하게 생성자 매개변수를 지정하지 않고 생성하는 것도 가능한데, 이 경우 String은 배열 길이 0인 문자 배열이 메모리에 할당되는 반면, StringBuffer는 기본 크기 16인 문자 배열이 메모리에 할당된다. 만약 문자열이 매개 변수로 사용되면서, 매개변수 문자열 길이가 기본값인 16을 초과하면, 문자열 길이만큼 문자 배열이 메모리에 추가로 할당된다.
생성자를 통해 문자열을 선언, 초기화하는 방식은 아래를 참고하자.
strBuffer1의 경우, 문자열이 지정되지 않았지만 이미 strBuffer1 참조변수가 참조하는 메모리는 16개의 문자 배열이 존재하고 있다. 또한 strBuffer1은 문자를 문자열을 추가(append)하거나 삭제(delete)함으로써 메모리에 저장된 값을 수정한다.
StringBuffer의 참조변수는 System.out.print나 toString()으로 출력하면 String과 동일하게 참조변수에 할당된 문자열 값이 출력된다.
2. StringBuffer 클래스의 매서드
StringBuffer 클래스는 String과 동일하게 CharSequence 인터페이스를 구현한 클래스다. String의 대부분 매서드가 CharSequence 인터페이스에 기본적으로 정의되어 있기에, StringBuffer 클래스 역시 String에서 사용 가능한 거의 모든 매서드를 동일하게 사용할 수 있다.
하지만 StringBuffer는 String과 달리 mutable이라는 특성을 지니기 때문에 String 클래스에서 사용하지 않는 매서드 또는 유사한 기능을 다른 이름으로 구현한 매서드가 존재한다.
(1) StringBuffer append()
append() 매서드는 인스턴스 StringBuffer에 문자열을 추가하는 매서드다. 매개 인자로는 Java의 모든 기본형은 물론이고, CharSequence, Object, String, StringBuffer 등의 참조객체도 지정할 수 있다.
위의 예시에서 보듯 매개인자 참조타입에 관계없이 모든 매개 인자를 문자열로 변경하여 메모리에 추가한다. 만약 기존의 StringBuffer가 문자 배열 기본 크기인 16을 지닌 상태에서 append()로 추가된 총 문자열 길이가 기본 배열 길이를 넘어가면, StringBuffer는 배열 크기를 16씩 추가한다.
참고로 String 클래스는 concat()이라는 매서드를 사용하거나 "+" 연산자로 문자열을 추가한다. StringBuffer와 달리 동일 메모리 내의 값 변경 또는 메모리 공간 증가가 아닌, 별도의 메모리에 추가 문자열을 저장하고 참조변수로 추가 문자열이 저장된 메모리 주소를 가리키게 한다. 자세한 내용은 필자의 이전 Java 포스팅을 참고하자.
(2) StringBuffer delete(int startIndex, endInt), StringBuffer deleteCharAt(int index) 매서드
delete()와 deleteCharAt() 매서드는 StringBuffer 문자열의 일부를 삭제하는 매서드다. delete() 는 인자로 삭제를 시작할 문자열의 index와 삭제를 완료할 문자열의 마지막 index + 1 값을 지정해주면 되며, deleteCharAt()은 문자열 내에서 삭제할 문자의 index를 지정해주면 된다.
참고로 delete(), deleteCharAt()이 문자열 가운데의 값을 삭제하는 경우, 삭제된 부분은 빈 값으로 남는 것이 아니라 삭제된 문자 배열 뒷 부분의 문자가 해당 자리로 이동한다.
[ append, delete() 매서드로 참조변수의 메모리 주소 변경 여부 확인 ]
StringBuffer는 메모리 주소 내의 값을 변경할 수 있는 mutable 클래스다. 실제로 그런지 확인해보자.
이전 포스팅에서 String 인스턴스를 생성했을 때와는 다르게, 문자열 변경 후에도 메모리 주소가 그대로 유지되고 있음을 확인할 수 있다.
(3) int capacity(), void trimToSize() 매서드
StringBuffer 클래스의 특성으로 인해, StringBuffer로 문자열을 다루다 보면 참조변수가 가리키는 메모리의 배열 크기를 알아야 할 때가 있다. 이 때 사용하는 매서드가 capacity()로 이름의 의미에서 알 수 있듯이 참조변수가 가리키는 메모리에 할당된 배열 크기(capacity)를 반환해주는 역할을 한다.
위의 예시에서 strBuffer3처럼, 저장할 문자열의 길이보다 문자 배열 주소 길이가 긴 경우가 발생한다면 메모리 낭비가 발생하게 된다. StringBuffer 클래스는 문자열의 크기만큼 메모리를 조절해주는 trimToSize()라는 매서드가 존재하는데, 이 매서드를 적용하는 순간, 참조변수가 참조하는 배열 중 불필요한 크기만큼 날려버리게 된다.
(4) StringBuffer insert (int insertIndex, 추가_매개인자)
insert()는 문자열의 특정 위치(insertIdex)에 기본형 값 또는 참조형 값을 문자열 형태로 추가하는 매서드다. append와 마찬가지로 추가 매개인자는 boolean, int, short 등등 기본형은 전부 사용이 가능하며, Object, String, StringBuffer 등의 참조 변수도 매개인자로 지정할 수 있다.
사실 StringBuffer의 append() 매서드는 insert() 매서드를 기반으로 만들어진 매서드다. insertIndex 부분을 기본값으로 기존 문자 배열의 크기'로 정의했기 때문에 append() 매서드만으로도 기존 문자열 뒤에 새 문자열 추가가 가능한 것이다. 위의 예시 코드에서 빨간 박스 부분을 참고하자.
(5) StringBuffer reverse() 매서드
StringBuffer 클래스에서 마지막으로 살펴볼 매서드는 reverse()다. 이름 그대로, 배열 내 저장된 문자열의 배열 순서를 거꾸로 저장하는 기능을 한다. 크게 어려운 내용은 아니라 예시만 추가하고 넘어간다.
2. StringBuilder 클래스
문자열을 다루기 위한 Java의 클래스는 String, StringBuffer, StringBuilder 세 클래스가 있다. StringBuffer는 mutable한 특징으로 메모리 공간 낭비를 줄일 수 있다는 장점이 있지만, 지금까지 필자가 포스팅에서 작성해왔던 싱글 스레드(Single Thread, 하나의 프로세스로만 프로그램이 동작)가 아닌 멀티 스레드에서는 StringBuffer가 안전을 위해 동기화(Synchronize)된다고 한다. 이 경우 싱글 스레드에서는 StringBuffer의 성능이 동기화로 인해 낮아지기 때문에 Java에서는 StringBuffer의 스레드 동기화를 제거한 StringBuilder를 제공한다고 한다.
StringBuilder는 StringBuffer에서 일부 기능만 제거한 것이기 때문에 문자열 인스턴스 생성과 클래스 매서드 사용 방법은 StringBuffer 클래스와 동일하다. 추후 멀티 스레드(Multi Thread) 내용에 대해 포스팅하게 되면 관련 링크를 추가할 예정이다.
3. 문자열 인코딩(String Encoding)
프로그래밍에서 문자는 1 Byte, 8 bit 의 공간을 차지한다. 이 1 Byte 공간으로 표현할 수 있는 문자는 127(2^7)가지로, 초창기의 컴퓨터 공학에서는 사람의 문자를 컴퓨터에게 인식시키기 위해 ASCII 코드라는 것을 만들었다. 하지만 컴퓨터가 대중화되고 지구가 세계화되면서 지구상의 모든 문자를 1 Byte로 표시할 수 없게 되자, 2 Byte 문자 체계인 유니코드(Unicode)가 등장한다.
하지만 유니코드도 전 세계 문자를 모두 표시할 수 없기 때문에 20 bit (2.5 Byte)로 확장하게 되며, 이 외에도 문자를 표현할 수 있는 여러 방식이 나타나게 된다. 2000년대 초반만 하더라도 해외 인터넷 사이트를 접속하면 글자가 깨지는 현상을 비일비재하게 경험할 수 있었는데, 서버에서 제공하는 charset과 클라이언트의 charset 값이 다르게 설정되는 것이 원인이었다.
* charset : ASCII, Unicode 등을 통칭하는 단어다.
문자열 인코딩은 Byte로 표시된 문자열을 사람이 알아볼 수 있도록 만드는 것이다. 반대의 개념은 디코딩이라 부르며, 문자를 각 Charset이 가지는 비트에 맞게 변환한 숫자로 나타낸다.
(1) Java에서 제공하는 Charset
Java 패키지 중 java.nio.charset 패키지에 존재하는 Charset 클래스에는 availableCharsets()라는 매서드가 존재한다. 이 매서드는 Java에서 사용가능한 charset의 종류를 화면에 출력하는 기능을 한다.
(2) 문자열 인코딩 변환
본격적으로 인코딩 변환을 진행해보자. 문자 인코딩 변환을 위해서는 문자를 구성하는 Byte 값을 먼저 알아야 한다. 이 Byte 값을 알아내는 과정을 디코딩(Decoding)이라고 하며, 이 과정이 선행되어야 charset을 이용하여 인코딩을 진행할 수 있기 때문이다. 문자열의 디코딩을 진행하는 매서드는 String 클래스에 정의된 getBytes() 매서드다. 이 매서드는 인코딩을 변경할 문자열 인스턴스를 통해 호출하고 매개 인자로 charSet 종류를 받으며, 결과값으로 변환된 Byte 값을 배열 형태로 돌려준다.
getBytes()
getBytes(String charSet명)
매서드는 두 가지 형태로 사용이 가능한데, 매개변수를 별도로 지정하지 않는 경우, Java의 기본 인코딩 charset인 UTF-8의 디코딩 값을 배열 형태로 반환한다.
참고로 getBytes(charSet명) 매서드는 UnsupportedEncodingException을 던지도록(Throws) 정의되어 있으므로, 사용 시 getBytes(charSet명)가 사용되는 매서드에 throws 구문을 적용해야 한다. 필자는 "안녕" 이라는 문자열을 여러 charset으로 디코딩 해보려 한다.
모두 동일한 "안녕" 이라는 문자열에 대해 작업을 진행했지만, 인코딩 종류에 따라 문자에 할당되는 비트 수가 각각 다르기 때문에, Byte 배열값은 인코딩 종류마다 다르게 나타난다.
이제 필자는 UTF-8 Byte 값으로 저장된 getDefaultByte를 String 인스턴스로 호출할 것이다. 인코딩 Byte로 String 인스턴스를 호출하는 방법은 아래의 Documentation 발췌 내용을 참고하자.
기존의 "안녕" 이라는 문자열을 각기 다른 인코딩 코드로 인스턴스화하고 출력을 진행하면, 위에서 보는 것처럼 글자가 전부 깨져서 나온다(변환이 잘 된 것이다). 위의 예시에는 작성하기에 지면이 작아(?) 표시하지 않았지만, UTF-16 디코딩 값을 "UTF-16" charset으로 인코딩을 진행하면 "안녕"이라는 단어가 정상적으로 출력된다.
Eclipse를 사용하지 않고, 메모장으로 한글 출력 실습을 진행하는 경우, 클래스 파일 안에 한글이 입력된 경우 컴파일 과정에서 Encoding 관련 에러가 발생하게 된 것을 경험한 분들도 있을 것이다. 이 원인은 Window 메모장에서 한글 입력에 사용하는 charset이 X-window-949라는 녀석인데, Java는 메모장에서 작성된 클래스 파일에 한글이 포함된 경우 기본 charset인 utf-8로 디코딩을 진행하고, x-windows-949로 인코딩을 진행하기 때문에 글자가 깨지게 된다.
따라서 cmd에서 한글이 포함된 클래스 파일을 컴파일 해야 할 경우, 아래와 같이 javac의 -encoding 옵션을 사용하여, utf-8 charset으로 클래스 파일 내의 모든 글자를 인코딩하라고 지정해주어야 한다.
다음 포스팅에서는 래퍼(Wrapper) 클래스와 기타 Java 사용 시 유용한 매서드들 몇 개만 추려서 알아보려한다.
Fin.
'Java > Java Basic' 카테고리의 다른 글
[Java Basic] 29. 날짜 및 시간 관련 클래스 (0) | 2022.08.24 |
---|---|
[Java Basic] 28. Wrapper 클래스와 기타 클래스 (0) | 2022.08.22 |
[Java Basic] 26. java.lang.String 클래스 (0) | 2022.08.15 |
[Java Basic] 25. java.lang.Object 클래스 (0) | 2022.08.12 |
[Java Basic] 24. 예외 발생시키기와 사용자 지정 예외 생성 (0) | 2022.08.08 |
댓글