Go에서 String과 Bytes Slice 이해하기
preview image

이 글에서는 유니코드와 UTF-8 인코딩에 대한 자세한 내용은 다루지 않으니, 미리 가볍게 학습해두는 것을 추천한다.

Go 언어에서 소스 코드는 UTF-8로 인코딩된다.

그래서 소스 코드 안에 직접 작성한 모든 문자열을 UTF-8으로 인코딩하기 때문에 소스 코드에 “가나다"라고 쓰면 이 문자열은 UTF-8로 인코딩된 문자열이 되는 것이다.

(* UTF-8은 유니코드 포인트를 나타내기 위해 1바이트에서 최대 4바이트까지 바이트 수가 가변적이다)

for i, r := range "가나다" {
  fmt.Println(i, r)
}
fmt.Println(len("가나다"))

// 0 44032
// 3 45208
// 6 45796
// 9

위 코드의 for 문에서 i 변수는 “가나다” 각각의 유니코드 문자에 대한 바이트 위치(Index)를 가져오고, r 변수는 유니코드 코드 포인트(Decimal)를 가져온다.

i변수의 타입은 int, r변수의 타입은 rune인데 runeint32 타입의 별칭으로서 유니코드 포인트 하나를 담을 수 있다.

r변수에 담긴 유니코드 포인트를 실제 문자로 출력하고 싶으면 string(r)으로 감싸서 호출하면 된다. string() 함수는 유니코드 포인트를 해당하는 유니코드 문자열로 형변환을 해주는 역할을 한다.

위 코드의 실행 결과에서 i 변수의 값은 0, 3, 6 순서로 출력된다.
그 이유는 “가”, “나”, “다"를 UTF-8으로 표현하는데 글자당 3바이트가 필요하기 때문이다. 결국 “가나다"는 총 9바이트가 된다.

이 원리를 활용하면 문자열을 바이트 단위로 다룰 수도 있다.

위에서 사용된 for 문을 살짝 변형하여 문자열의 길이(9바이트)만큼 바이트 단위로 출력해보자.
아래 코드에서 사용한 내장 함수 len()string 값을 전달하면 바이트 길이를 반환하고, fmt.Printf()함수에 전달한 %d포맷은 바이트마다 UTF-8 숫자 값을 출력한다.

foo := "가나다" // 9바이트
for i := 0; i < len(foo); i++ {
  fmt.Printf("%d ", foo[i])
}

// 234 176 128 235 130 152 235 139 164

이렇게 문자열을 바이트 단위로 다룰 수 있기 때문에 모든 바이트를 슬라이스에 담게 되면 이것이 바이트 슬라이스([]byte)가 된다.

위에서 했던 것과 반대로 string 타입을 []byte 타입으로 형변환 하는 방법은 다음과 같다.

bar := []byte(foo)
fmt.Printf("%d", bar)
// [234 176 128 235 130 152 235 139 164]

이 결과로 알 수 있는 사실은 바이트 슬라이스는 단지 유니코드 포인트의 UTF-8 인코딩을 담은 바이트 리스트일 뿐이다.

아래 코드는 bar 바이트 슬라이스 변수에 담긴 9바이트 중 앞에 3바이트만 string타입으로 출력한 결과이다.

bar2 := bar[0:3]
fmt.Printf("%s", bar2)
// 가

즉, 234, 176, 128 이라는 값은 UTF-8 인코딩이 “가"를 유니코드 포인트로 나타내기 위해 3바이트를 사용하고 있다는 것을 의미한다.

마지막으로 문자열을 string 타입으로 다루는 것과 []byte 타입으로 다루는 것에 중요한 차이점은 값을 직접 조작할 수 있는지다.
[]byte 타입은 값을 교체할 수 있고 크기 조절이 가능한 연속적인 바이트 리스트지만, string은 값을 수정할 수 없고 고정된 크기의 연속된 바이트 리스트이다.
따라서 string 타입의 값은 언제나 새로운 문자열을 생성할 수밖에 없게 된다.

string타입은 이러한 불변의 특징 때문에 map의 키로 사용될 수 있고, []byte 타입은 바이트 스트림을 처리하는 로우(raw) 바이트를 다룰 때 사용하기 좋다.


comments powered by Disqus
TAGS
On this page