문자열 인코딩

Hyeseong·2022년 12월 4일
0

TIL

목록 보기
13/13

문자열 인코딩

  • 문자열 인코딩 : 2진법을 사용하는 컴퓨터가 인간의 언어를 일정한 규칙에 따라 2진수로 변환하는 방식
  • 컴퓨터는 인간이 만든 2진수와 문자를 1:1 대응하는 규칙을 통해 2진수로 문자를 처리함
    - 예. ASCII, EUC-KR, UTF-8, UTF-16, UTF-32
  • 2진수 01000001을 10진수로 읽으면 65가 되지만, 아스키 코드로 읽으면 문자 A가 됨
  • 파이썬 format() 함수를 활용하면 2진수를 다른 진수 및 문자열로 표현 할 수 있음
# 2진수를 10진수로 처리 
print('10진수(01000001)={0}'.format(0b01000001))

# 2진수를 16진수로 처리 
print('16진수(01000001)={0}'.format(hex(0b01000001)))

# 2진수를 문자로 처리 
print('16진수(01000001)={0}'.format(chr(0b01000001)))
10진수(01000001)=65
6진수(01000001)=0x41
16진수(01000001)=A

아스크 코드

  • 아스키코드는 처음으로 표준을 정립한 문자열 인코딩 방식
  • 여전히 많이 사용
  • 대문자, 소문자, 아라비ㅏ숫자, 공백, 특수문자
  • 0 ~ 127개, 총 128개의 숫자를 사용

hello 문자열을 아스키 코드로 출력

#ascii.py

def print_text(text, encoding_type):
	byte_data = text.encode(encoding_type)
    hex_data_as_str = ' '.join("{0}".format(hex(c)) for c in byte_data)
    int_data_as_str = ' '.join("{0}".format(int(c)) for c in byte_data)
    
    print('\'' + text + '\' 문자열 길이: {0}'.format(len(text)))
    print('\'' + text + '\' 전체 문자를 표현하는 데 사용한 바이트 수: {0} 바이트'.format(len(byte_data)))
    print('\'' + text + '\' 16진수 값 : {0}'.format(hex_data_as_str))
    print('\'' + text + '\' 10진수 값 : {0}'.format(int_data_as_str))
 
print_text("Hello", 'ascii')

EUC-KR

  • 대한민국에서는 컴퓨터로 한글을 표현하는 방법으로 EUC-KR 문자 집합을 만듬
  • 한국 산업 표준(Korean Industrial Standards)으로 지정된 한국어 문자 집합으로 문자 하나를 표현하기 위해 2바이트를 사용
  • CP949는 EUC-KR을 확장한 문자 집합으로 EUC-KR과 같은 문자열 인코딩이나, 더 많은 문자를 표현할 수 있습니다. 현재는 EUC-KR로 표기하더라도 실제로는 CP949 문자 집합을 사용하는 경우가 많음.

EUC-KR로 'HELLO', '안녕하세요'문자열을 출력하기

def print_text(text, encoding_type):
	byte_data = text.encode(encoding_type)
    hex_data_as_str = ' '.join("{0}".format(hex(c)) for c in byte_data)
    int_data_as_str = ' '.join("{0}".format(int(c)) for c in byte_data)
    
    print('\'' + text + '\' 문자열 길이: {0}'.format(len(text)))
    print('\'' + text + '\' 전체 문자를 표현하는 데 사용한 바이트 수: {0} 바이트'.format(len(byte_data)))
    print('\'' + text + '\' 16진수 값 : {0}'.format(hex_data_as_str))
    print('\'' + text + '\' 10진수 값 : {0}'.format(int_data_as_str))
    
print_text('Hello', 'euc-kr')
print_text('안녕하세요', 'euc-kr')

영문자 "Hello"를 출력할 떄 아스키 코드와 동일하게 5바이트를 사용했지만, 한글 "안녕하세요"를 출력하기 위해서는 10바이트를 사용했습니다. EUC-KR로 아스키 코드 영역에 있는 글자를 표현할 때는 1바이트를 사용하지만, 한글 문자를 표현할 때는 2바이트를 사용하기 때문입니다.

이처럼 문자열 인코딩에서는 실제 문자열 길이가 버퍼 길이와 다른 경우가 많습니다. 실제 문자열 길이는 사람 눈에 보이는 문자 길이에 해당하고, 버퍼 길이는 컴퓨터가 문자를 표현하는 데 사용한 바이트 수를 의미합니다. 여기서 버퍼는 메모리에 할당된 공간을 뜻합니다.(예를 들어 변수를 선언해 숫자나 문자열 값을 넣거나, 새로운 객체를 생성하는 행위 등 모두 버퍼가 필요함). 그래서 실제 문자열 길이와 컴퓨터가 할당하는 버퍼 크기는 항상 다를 수 있다는 점

TIP

개발 환경에서는 실제 문자열 길이와 컴퓨터가 할당하는 버퍼 크기를 동일하게 취급해 생기는 버그가 생각보다 많습니다. 문자열을 취급할 때는 어떤 문자열 인코딩 방식을 쓰는지 반드시 알아야 하고, 가능한 같은 문자열 인코딩을 사용하는 것이 좋음.

UNICODE

  • 국가별로 독자적인 문자열 인코딩을 사용하는 문제를 해결하기 위해 국제표준화기구(ISO)에서 동일한 규칙으로 모든 언어를 표현할 수 있는 유니코드 문자 집합을 만듬
  • 최초 버전(1.0), 1991 -> 12.1버전

UTF-8

  • UTF8은 8비트(1바이트)로 인코딩하는 것을 의미
  • 아스키코드와 완벽하게 호환
  • 최소 1바이트에서 최대 6바이트까지 사용

TIP

보통 일반적인 문자는 3바이트 내로 처리되며, 4바이트 영역에는 이모지같은 문자가 있습니다.
고대 문자 같은 것을 사용하지 않는 한 5바이트 이상을 쓰는 경우는 거의 없습니다.

UTF-8 문자열 출력

def print_text(text, encoding_type):
	byte_data = text.encode(encoding_type)
    hex_data_as_str = ' '.join("{0}".format(hex(c)) for c in byte_data)
    int_data_as_str = ' '.join("{0}".format(int(c)) for c in byte_data)
    
    print('\'' + text + '\' 문자열 길이: {0}'.format(len(text)))
    print('\'' + text + '\' 전체 문자를 표현하는 데 사용한 바이트 수: {0} 바이트'.format(len(byte_data)))
    print('\'' + text + '\' 16진수 값 : {0}'.format(hex_data_as_str))
    print('\'' + text + '\' 10진수 값 : {0}'.format(int_data_as_str))

print_text('Hello', 'utf8')
print_text('안녕하세요', 'utf8')
'Hello' 문자열 길이: 5
'Hello' 전체 문자를 표현하는 데 사용한 바이트 수: 5 바이트
'Hello' 16진수 값 : 0x48 0x65 0x6c 0x6c 0x6f
'Hello' 10진수 값 : 72 101 108 108 111
'안녕하세요' 문자열 길이: 5
'안녕하세요' 전체 문자를 표현하는 데 사용한 바이트 수: 15 바이트
'안녕하세요' 16진수 값 : 0xec 0x95 0x88 0xeb 0x85 0x95 0xed 0x95 0x98 0xec 0x84 0xb8 0xec 0x9a 0x94
'안녕하세요' 10진수 값 : 236 149 136 235 133 149 237 149 152 236 132 184 236 154 148

"Hello"를 출력할 때는 아스키 코드와 동일하게 5바이트를 사용합니다. 하지만 "안녕하세요"를 출력할 떄는 15바이트를 사용했습니다.

UTF-16

  • UTF-16은 16비트(2바이트)로 인코딩하는 것을 의미함
  • UTF-16은 2바이트 또는 4바이트만 사용하기 때문에 아스키 코드와 호환되지 않음
def print_text(text, encoding_type):
	byte_data = text.encode(encoding_type)
    hex_data_as_str = ' '.join("{0}".format(hex(c)) for c in byte_data)
    int_data_as_str = ' '.join("{0}".format(int(c)) for c in byte_data)
    
    print('\'' + text + '\' 문자열 길이: {0}'.format(len(text)))
    print('\'' + text + '\' 전체 문자를 표현하는 데 사용한 바이트 수: {0} 바이트'.format(len(byte_data)))
    print('\'' + text + '\' 16진수 값 : {0}'.format(hex_data_as_str))
    print('\'' + text + '\' 10진수 값 : {0}'.format(int_data_as_str))
    
print_text('Hello', 'utf-16')
print_text('안녕하세요', 'utf-16')
'Hello' 문자열 길이: 5
'Hello' 전체 문자를 표현하는 데 사용한 바이트 수: 12 바이트
'Hello' 16진수 값 : 0xff 0xfe 0x48 0x0 0x65 0x0 0x6c 0x0 0x6c 0x0 0x6f 0x0
'Hello' 10진수 값 : 255 254 72 0 101 0 108 0 108 0 111 0
'안녕하세요' 문자열 길이: 5
'안녕하세요' 전체 문자를 표현하는 데 사용한 바이트 수: 12 바이트
'안녕하세요' 16진수 값 : 0xff 0xfe 0x48 0xc5 0x55 0xb1 0x58 0xd5 0x38 0xc1 0x94 0xc6
'안녕하세요' 10진수 값 : 255 254 72 197 85 177 88 213 56 193 148 198

바이트 순서 표시

UTF-16, UTF-32는 바이트 순서 표시(byte order mark) | BOM을 사용합니다. BOM은 문자열 가장 맨 앞 2바이트에 0xFEFF(유니코드로 U+FEFF)로 표기하여 사용한다는 것을 의미합니다. 또한 0xFE와 0xFF 중 어떤 문자가 먼저 오는지에 따라 리틀 엔디언 (LE)과 빅 엔디언(BE)으로 나뉩니다. 그래서 두 방식에 따라 문자열 인코딩 시 바이트 데이터를 조합하는 순서가 바뀌게 됩니다.

BOM을 이용하여 바이트 표현 순서를 정하는 이유는, CPU 설계에 따라 바이트 값을 처리하는 순서가 다르기 때문입니다. 같은 0xFEFE를 CPU가 읽을 때 리틀 엔디언 방식은 0xFF 다음 0xFE을 읽으며, 빅 엔디언 방식은 0xFE 다음 0xFF를 읽음.

빅엔디언, 리틀 엔디언

종류0x1234표현0x12345678표현
빅엔디언12 3412 34 56 78
리틀 엔디언34 1278 56 34 12

빅 엔디언은 문자를 구성하는 바이트 두 개 [12], [34]중 큰 단위인 [12]가 먼저 나옵니다. 반대로 리틀 엔디언은[12], [34]중 작은 단위인 [34]가 먼저 나오는 겁니다.

위 코드, UTF16은 0xFF가 먼저 나왔으므로 리틀 엔디언(UTF-16-LE) 인코딩으로 볼 수 있음. 리틀 엔디언은 뒷자리부터 읽으므로 0x48, 0xC5, oxC548로 읽습니다. 오늘날 대부분의 개인 컴퓨터는 리틀 엔디언 방식을 사용함.

0xfe 0xff 0xc5 0x48 0xb1 0x55 0xd5 0x58 0xc1 0x38 0xc6 0x94

UTF-16 글자 인식 "Hello"

이전 [코드 1-5] 출력 결과를 살펴 봅시다. UTF-16은 특수한 경우를 제외하고는 모두 2바이트를 사용하니 2바이트로 끊어 읽으면 됩니다. 'Hello'문자열을 구성하는 각 문자를 표현하는 데 사용하는 값은 아래와 같습니다.

Hello를 구성하는 UTF-16 값

문자16진수
H0x48 0x00
e0x65 0x00
l0x6c 0x00
o0x6f 0x00

이 코드를 실행한 컴퓨터는 리틀 엔디언 방식을 사용하므로 거꾸로 조합하면 각각 0x48, 0x65, 0x6C, 0x6F입니다. 이 값들은 실제 UTF-16 코드 표에서 사용하는 값으로 'H'문자열을 예를 들면 다으모가 같습니다.

| H -> LATIN CAPITAL LETTER H(U+0048)

결론

UTF8

  • 가장 많이 사용하는 문자열 인코딩, 최소 1바이트 ~ 최대 6바이트
  • 대부분 4바이트 내로 처리
  • 아스키 코드와 호환 가능
  • 윈도우, 자바, 임베디드를 제외한 거의 모든 환경에서 문자열 처리 표준
  • JSON은 UTF-8 인코딩만 사용하며, 다른 문자열 인코딩은 표준에서 지원하지 않음

UTF16

  • 자바와 윈도우는 유니코드를 사용하기 전부터 고정된 2바이트 길이의 문자 집합을 사용함 그래서 UTF-16은 멀티 바이트라고함. 두 환경에서의 호환성 외에 UTF16을 사용할 별다른 이유는 없음
  • 2바이트 or 4바이트 길이의 문자열을 사용하며, 아스키 코드와 호환되지 않음
  • UTF-16 기반 환경에서 UTF-8을 사용할 때는 사용 영역을 명확히 구분하는게 좋음. 예를 들어 자바 기반 웹 서비스는 UTF16을 사용하되, 외부(DB<->Browser) 통신 시 UTF8로 변환하여 사용하는게 좋음

UTF32

  • 4바이트를 고정적으로 사용
  • 반드시 UTF32를 사용해야 하는 환경이 아니라면 사용하지 않음

EUC-KR

  • 한국에서 독자적으로 사용하는 문자열 인코딩이며 고정된 2바이트를 사용
  • 가능하다면 UTF-8로 바꾸는게 좋지만 현실적으로 어려운 경우가 많음. UTF16과 마찬가지로 경계를 명확히 사용하는게 좋음

체크 포인트

  • MYSQL의 UTF8타입에는 utf8과 utf8mb4가 있음.
    - utf8은 3바이트까지 정상 처리하지만 4바이트 영역 문자는 처리하지 못함
    • utf8mb4 UTF8과 완벽히 호환되는 문자 집합
    • MYSQL 설정을 통해 변경 가능
  • 국내에서 만든 서비스를 연동할 때는 EUC-KR을 사용하는 경우가 있으니 주의
    - 집필 시점에서 케이지이니시스 결제 모듈은 결제 요청 내역을 EUC-KR로 인코딩
profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글