개요
Go 언어에서 문자열(string)과 룬(rune)은 텍스트를 다룰때 주의할점과 특징들에 대해서 알아보겠습니다.
string(문자열)
Go에서 string은 불변(immutable)의 바이트 슬라이스입니다. 기본적으로 UTF-8로 인코딩된 텍스트를 나타내는 데 사용됩니다.
특징
- 읽기 전용: 한 번 생성된 문자열은 변경할 수 없습니다.
- UTF-8 인코딩: 기본적으로 UTF-8을 사용합니다.
- 인덱싱: 개별 바이트에 접근할 수 있지만, 항상 유효한 유니코드 문자를 반환하지는 않습니다.
예제
s := "Hello, 월드"
fmt.Println(len(s)) // 13 (바이트 수)
fmt.Println(s[0]) // 72 (ASCII 값 'H')
rune(룬)
rune은 Go에서 유니코드 코드 포인트를 나타내는 타입입니다. int32의 별칭으로, 하나의 유니코드 문자를 표현합니다.
특징
- 32비트 정수: 모든 유니코드 문자를 표현할 수 있습니다.
- 문자 단위 처리: 멀티바이트 문자를 단일 단위로 처리합니다.
- 문자열 순회에 유용: for range 루프와 함께 사용하면 문자열의 각 문자를 올바르게 처리할 수 있습니다.
예제
s := "Hello, 월드"
for _, r := range s {
fmt.Printf("%c ", r)
}
// 출력: H e l l o , 월 드
string과 rune의 관계
string은 바이트의 시퀀스지만, 대부분의 경우 우리는 문자열을 문자(character)의 시퀀스로 생각합니다. Go에서 이러한 문자는 rune으로 표현됩니다.
문자열을 rune 슬라이스로 변환
s := "Hello, 월드"
r := []rune(s)
fmt.Println(len(r)) // 8 (문자 수)
rune 슬라이스를 문자열로 변환
r := []rune{'H', 'e', 'l', 'l', 'o', '월', '드'}
s := string(r)
fmt.Println(s) // "Hello월드"
한글과 같은 멀티바이트 문자의 이슈
한글은 UTF-8 인코딩에서 3바이트를 차지합니다. 이로 인해 문자열 처리 시 몇 가지 주의할점을 알아두어야합니다!
이슈
- 문자열 길이: len() 함수는 바이트 수를 반환하므로, 한글을 포함한 문자열의 실제 문자 수와 일치하지 않습니다.
- 인덱싱: 문자열을 바이트 단위로 인덱싱하면 한글 문자의 중간 바이트에 접근할 수 있어 의미 없는 값이 나올 수 있습니다.
- 슬라이싱: 바이트 단위로 슬라이싱하면 한글 문자가 잘릴 수 있습니다.
s := "Hello, 세계"
fmt.Println(len(s)) // 13 (바이트 수)
fmt.Println(s[7]) // 236 (의미 없는 바이트 값)
fmt.Println(s[7:9]) // "세" (한글 문자의 일부만 포함)
이처럼 한글은 3바이트 이기때문에 13바이트의 길이로 계산되어지는 것을 알 수 있어요.
rune과 utf8 패키지로 이슈 해결
Go 언어에서 이러한 이슈를 해결하기 위해 두 가지 접근방식에 대해서 알아볼께요.
- rune 타입 사용:
- rune은 하나의 Code points로 나타내므로, 멀티바이트 문자를 정상적으로 처리할수 있습니다.
- for range 루프는 문자열을 자동으로 rune 단위로 순회합니다.
- utf8 패키지 활용:
- utf8 패키지는 UTF-8 인코딩된 텍스트를 다루기 위한 함수들을 제공합니다.
- Valid(b []byte) bool
- 주어진 바이트 슬라이스가 유효한 UTF-8 인코딩인지 검사합니다.
- ValidRune(r rune) bool
- 주어진 룬이 유효한 유니코드 코드 포인트인지 검사합니다.
- RuneStart(b byte) bool
- 주어진 바이트가 UTF-8 인코딩의 첫 바이트인지 검사합니다.
- EncodeRune(p []byte, r rune) int
- 주어진 룬을 UTF-8로 인코딩하여 바이트 슬라이스에 저장합니다.
- DecodeLastRune(p []byte) (r rune, size int)
- UTF-8로 인코딩된 바이트 슬라이스에서 마지막 룬을 디코딩합니다.
- RuneLen(r rune) int
- 주어진 룬을 UTF-8로 인코딩했을 때 필요한 바이트 수를 반환합니다.
- Valid(b []byte) bool
- utf8 패키지는 UTF-8 인코딩된 텍스트를 다루기 위한 함수들을 제공합니다.
문자 수 세기
s := "Hello, 월드"
fmt.Println(utf8.RuneCountInString(s)) // 9
- utf8.RuneCountInString() 함수는 문자열에 포함된 유니코드 문자(rune)의 수를 세는 함수입니다.
- 이 함수는 멀티바이트 문자(예: 한글)도 하나의 문자로 취급합니다.
- 결과값 9는 'H', 'e', 'l', 'l', 'o', ',', ' ', '월', '드' 총 9개의 문자를 의미합니다.
- 단순히 len(s)를 사용했다면 바이트 수인 13가 반환됩니다.
안전한 인덱싱과 슬라이싱
s := "Hello, 월드"
runes := []rune(s)
fmt.Println(string(runes[7])) // "월" (올바른 문자 출력)
fmt.Println(string(runes[7:])) // "월드" (올바른 슬라이싱)
- 문자열 s를 []rune 슬라이스로 변환합니다. 이 과정에서 각 문자는 하나의 rune으로 변환됩니다.
- runes[7]은 8번째 문자(0부터 시작)인 '월'에 해당합니다.
- string(runes[7])은 해당 rune을 다시 문자열로 변환합니다.
- runes[7:]는 8번째 문자부터 끝까지의 슬라이스를 의미하며, 이를 문자열로 변환하면 "월드"가 됩니다.
- 이 방법은 바이트 단위로 인덱싱하거나 슬라이싱할 때 발생할 수 있는 문제(멀티바이트 문자 깨짐)를 방지합니다.
문자열 순회
s := "Hello, 월드"
for i, r := range s {
fmt.Printf("%d: %c\n", i, r)
}
// 출력:
// 0: H
// 1: e
// 2: l
// 3: l
// 4: o
// 5: ,
// 6:
// 7: 월
// 10: 드
- for range 루프는 문자열을 자동으로 rune 단위로 순회합니다.
- i는 각 rune의 시작 바이트 인덱스를 나타냅니다.
- r은 각 rune(유니코드 코드 포인트)을 나타냅니다.
- 출력 결과를 보면:
- 영문자와 특수문자는 각각 1바이트를 차지하므로 인덱스가 1씩 증가합니다.
- '월'은 3바이트를 차지하므로 그 다음 문자 '드'의 인덱스는 7에서 10으로 건너뜁니다.
- range 순회방법은 멀티바이트 문자를 포함한 문자열을 안전하게 순회할 수 있게 해줍니다.
정리
- Go 언어에서 문자열 처리 시 rune 타입과 utf8 패키지 활용이 중요
- 멀티바이트 문자 처리 시 항상 주의 필요
- 안전한 문자열 처리를 위해 제공되는 기능들을 적절히 활용해야 함
참고
'프로그래밍 > Golang' 카테고리의 다른 글
Golang에서 문자열 자르기 (0) | 2024.08.07 |
---|---|
Golang 정수 타입과 아키텍처 의존성 (0) | 2024.08.06 |
Golang: 동일성(Identity) vs 동등성(Equality) (0) | 2024.08.03 |
Golang 값을 전달할까, 포인터를 전달할까? (0) | 2024.08.02 |
Golang map(맵) : 기본개념 (0) | 2024.08.01 |