1. 유니코드란 무엇인가?
유니코드의 배경
유니코드는 전 세계의 모든 문자를 통일된 방식으로 표현하기 위해 고안된 표준이다. 과거 ASCII는 128개의 문자만을 표현할 수 있었으며, 이는 영어를 제외한 다른 언어를 지원하지 못하는 문제가 있었다. 유니코드는 각 문자를 고유한 코드 포인트(Code Point)로 표현하여 이러한 문제를 해결했다.
코드 포인트와 인코딩
- 코드 포인트: 유니코드에서 각 문자를
U+XXXX형식의 고유 번호로 표현된다. 예를 들어 ‘A’는U+0041로, ‘가’는U+AC00으로 표현된다. - 인코딩: 코드 포인트를 실제로 저장하거나 전송하려면 바이트로 변환해야 한다. 주요 인코딩 방식은
다음과 같다.
- UTF-8: 가변 길이 인코딩 방식으로, 효울적이고 가장 널리 사용된다.
- UTF-16: 16비트로 고정 길이 인코딩 방식이다.
- UTF-32: 32비트로 고정 길이 인코딩 방식이다.
2. UTF-8 인코딩 방식
UTF-8은 유니코드 문자를 1~4 바이트의 가변 길이로 인코딩하는 방식이다. 유니코드는 전 세계의 모든 문자를 표현하기 위해 수천 개의 코드 포인트를 사용한다. 모든 문자를 동일한 바이트 수로 인코딩하면 비효율적일 수 있다.
예를 들어, 영어 알파벳은 코드 포인트가 작아 1바이트로 충분하지만 한글, 중국어는 더 큰 코드 포인트를 가지므로 더 많은 바이트가 필요하다.
UTF-8에서 코드 포인트 범위에 따라 문자에 할당되는 바이트 수가 다르다.
| 코드 포인트 범위 (16진수) | 바이트 수 | 첫 바이트 | 추가 바이트 구조 |
|---|---|---|---|
| U+0000 ~ U+007F | 1바이트 | 0xxxxxxx | - |
| U+0080 ~ U+07FF | 2바이트 | 110xxxxx | 10xxxxxx |
| U+0800 ~ U+FFFF | 3바이트 | 1110xxxx | 10xxxxxx 10xxxxxx |
| U+10000 ~ U+10FFFF | 4바이트 | 11110xxx | 10xxxxxx 10xxxxxx 10xxxxxx |
가'의 유니코드 코드 포인트는U+AC00이다. 코드 포인트 범위는U+0800 ~ U+FFFF`이므로 3바이트로 인코딩된다.- 2진수로 변환하면
U+AC00은1010 1100 0000 0000이다. - 이를 3바이트로 분할하면, UTF-8은 규칙에 따라 첫 바이트는 4비트, 나머지 바이트는 6비트씩 나눈다.
10101100 00000000
아래 구조에 따라 위의 2진수를 3바이트로 인코딩한다.
1110xxxx 10xxxxxx 10xxxxxx
첫 4비트, 6비트, 6비트를 각각 대입하면 다음과 같다.
-> 1110 `1010` 10 `110000` 10 `000000`
-> 11101010 10110000 10000000
-> 0xEA 0xB0 0x80
흥미로운 패턴이다.
효율성: UTF-8은 가변 길이 인코딩을 통해 공간을 효율적으로 사용한다. 예를 들어, ASCII 문자는 1바이트, 유니코드의 큰 범위 문자는 2~4바이트로 표현되므로 전체 파일 크기를 줄일 수 있다.
명확한 구분: 첫 바이트의 비트 패턴 (0, 110, 1110, 11110)을 통해 각 문자의 바이트 수를 쉽게 식별할 수 있다.
추가 바이트는 항상 10으로 시작하므로 바이트 경계가 깨졌을 때도 복구 가능하다.
1바이트의 인코딩 시 비트 패턴을 10으로 시작하지 않은 이유는 추가 바이트 구조와 구분하기 위함인 것 같다.
그리고 ASCII 문자는 1바이트로 인코딩되므로 첫 바이트가 0으로 시작한다. 아마도 초기 ASCII 문자를 유지하기 위함인 것 같다.
3. python과 Unicode
python의 str은 유니코드 문자열을 나타낸다.
str은 유니코드 코드 포인트를 저장하며, 인코딩 방식에 따라 바이트로 변환된다.
str은 불변(immutable)이며, 인덱싱을 통해 개별 문자에 접근할 수 있다.
문자열이 생성되면 수정할 수 없다. 흔히 오해하는 것이 문자열 변수에 선언한 값을 변경할 수 있다는 것이다.
>>> a = 'hello'
>>> id(a)
4343245488
>>>
>>> a = 'world'
>>> id(a)
4343245552
하지만 이것은 새로운 문자열을 생성하고 변수에 할당하는 것이다.
출력 결과에서 id(a)의 값이 변경되었음을 확인할 수 있다. 이는 변수 a가 더 이상 ‘hello’ 객체를 참조하지 않고, 새로운 문자열 객체 ‘world’를 참조한다는 것을 의미한다.
4. interning
파이썬에서 문자열이 불변 객체이기 때문에 같은 문자열을 여러 번 생성하면 메모리를 낭비할 수 있다. 파이썬은 이를 해결하기 위해 문자열 interning 기능을 제공한다.
자주 사용되거나 불변 속성을 가진 객체를 재사용하기 위해 동일한 객체를 공유하는 최적화 기술이다.
interning 사례
- CPython은 -5부터 256까지의 정수를 인터닝한다. 이 범위의 정수는 실행 중 재사용되며, 새로 생성되지 않는다.
a = 100
b = 100
print(a is b) # True
c = 1000
d = 1000
print(c is d) # False (인터닝되지 않음)
- 불변 문자열 중 특정 조건을 만족하는 경우 인터닝된다.
- 알파벳과 숫자로만 이루어진 문자열
- 작은 길이의 문자열
- 문자열 리터럴(코드에 직접 작성된 문자열)
>>> a = 'hello'
>>> b = 'hello'
>>> a is b
True
>>> a = 'hello world'
>>> b = 'hello world'
>>> a is b
False
- 성능 이점
동일한 값을 가진 객체가 여러번 생성되는 대신, 하나의 객체를 공유하므로 메모리를 절약한다.
is연산자는 객체의 동일성을 비교하므로, 값 비교보다 빠르게 결과를 반환한다.