Unicode와 UTF-8 간단히 이해하기

Jeong Dowon
4 min readJan 1, 2019
Photo by Genie Austin on Unsplash

유니코드(Unicode)

옛날옛날 컴퓨터가 세상에 나왔을 때는 ‘영어’와 몇가지 ‘특수문자’만 사용했고 이를 저장하기 위해서 1 byte면 충분했다. (0~255) 시간이 흘러 다른 국가 사람들이 컴퓨터를 이용하다보니 자국어도 컴퓨터로 표시하고 싶어졌다. 그래서 1 byte 안에 임의대로 알파벳 대신 자기나라 글자를 할당해서 그럭저럭 쓸 수는 있었다. 그러나 네트워크가 발전하고 다른 사람 홈페이지를 들어갔더니 글자가 와장창 깨지고 만다. 그리하여 국제적으로 전세계 언어를 모두 표시할 수 있는 표준코드를 만들기로 했다. 바로 유니코드(Unicode)다. 참고로, 한글 ‘가’는 유니코드로 ‘U+AC00’이다. 왜 그러냐고 이유는 묻지마라. 약속이다.

유니코드는 글자와 코드가 1:1매핑되어 있는 ‘코드표'이다. (코드표)

UTF-8

유니코드를 통해 코드표가 정의되었다. 남은 것은 그 ‘코드'가 컴퓨터에 어떻게 저장되어야 하는 것이다. 다른 말로 인코딩(encoding)이라고 하는데, 컴퓨터가 이해할 수 있는 형태로 바꿔주는 것이다. 예를 들어, 길동이가 자기만의 암호체계를 만들었는데 ‘가'는 0001(=1)로, ‘나'는 0010(=2)이라고 하자. 길동이가 ‘가나나가'라고 말하면 컴퓨터에는 0001 0010 0010 0001로 저장한다. 글자당 1 byte로 할당해 4 byte로 저장할 수 있고 00010010 00100001처럼 2byte로 저장할 수도 있다. 이건 전적으로 길동이와 컴퓨터간의 약속이다. 길동이만 쓸 때는 문제가 없는데 전세계 사람들이 사용하려면 좀 더 잘 만들어야 할것이다.

UTF-8은 유니코드를 인코딩(encoding)하는 방식이다. 전세계에서 사용하는 약속이다.

UTF-8은 가변 인코딩방식이다. 쉬운 말로 하면 글자마다 byte 길이가 다르다는 것이다. ‘a’는 1 byte이고 ‘가'는 3 byte이다. 가변을 구분하기 위해 첫 바이트에 표식을 넣었는데 2 byte는 110으로 시작하고 3바이트는 1110으로 시작한다. 나머지 바이트는 10으로 시작한다. 왜 그러냐고 묻는다면, 그냥 ‘약속이다’라고 말하고 싶다.

출처: 위키피디아

코딩

Go를 이용하면 다른 프로그래밍 언어보다 좀 더 직관적으로 확인 할 수 있다. Go는 에디터에서 작성한 모든 문자열을 UTF-8로 인코딩하는데 이 문자열을 반복문으로 처리할 때 첫 번째 인자로 글자시작 byte의 index를 가져온다. 타언어들의 경우, 문자열에 대한 글자의 index를 가져오는데 Go는 이 부분을 보다 명확하게 한 것이다. 두 번째 인자는 int32 (혹은 rune이라고도 한다) 타입의 실제 인코딩된 데이터이다.

package main

import "fmt"

func main() {

word := "abc가나다"
for
i, r := range word {
fmt.Printf("%d %d 0x%x %c\n", i, r, r, r)
}
fmt.Println()
for i := 0; i < len(word); i++ {
fmt.Printf("%d(%c) ", word[i], word[i])
}
}
실행 결과

알파벳의 경우, UTF-8 인코딩 시 1 byte를 차지하므로 1씩 증가한다. 하지만 한글의 경우, 3 byte를 차지하므로 3씩 증가한다. int32는 총 4 byte로 유니코드의 모든 문자를 표현한다. 재미있는 건, word[i]로 접근하게 되면 uint8 타입의 데이터가 나오는데 바로 byte이다. 그래서 한글의 경우, 1 byte씩 읽게 되면 ASCII 코드를 읽게된다.

유니코드는 국제표준 문자표이고 UTF-8은 인코딩 방식이다. 구글링 하다보면 이에 대한 굉장히 많은 자료가 있지만 오히려 그 방대한 내용때문에 기본적인 개념이 헷갈리기도 한다. 헷갈린 사람에게 조금이나마 도움이 되었으면 좋겠다.

--

--