이슈
Mac에서 업로드한 파일의 이름을 데이터베이스에 저장하여 사용하고 있었는데 해당 값에 대한 검색 결과에 누락되어 제대로 동작하지 않는 문제를 겪었다. 살펴보니, 이는 유니코드의 정규화 차이 때문인 것을 알게 되었다.
원인
유니코드 정규화(Unicode normalization)는 모양이 같은 여러 문자들이 있을 경우 이를 기준에 따라 하나로 통합해 주는 일을 가리킨다. 유니코드 정규화에는 다음과 같은 네 가지 방법이 있다. 이중 한글 처리와 관련된 것은 NFD(소리 마디를 첫가끝 코드로 분해)와 NFC(첫가끝 코드를 소리 마디로 결합)이다.
NFD (Normalize Form D)
NFD는 모든 음절을 Canonical Decomposition(정준 분해)하여 한글 자모 코드를 이용하여 저장하는 방식이다.
- 한 (U+D55C) → ᄒ (U+1112) + ᅡ (U+1161) + ᆫ (U+11AB)
이 방식은 현대 한글과 옛 한글을 동일한 방식으로 저장한다는 장점이 있지만 NFC 방식과 비교하여 텍스트의 크기가 커진다는 문제가 있다.
NFD는 macOS 시스템에서 주로 사용한다.
NFC (Normalize Form C)
NFC는 모든 음절을 Canonical Decomposition(정준 분해) 후 Canonical Composition(정준 결합) 하는 방식이다.
- ᄒ (U+1112) + ᅡ (U+1161) + ᆫ (U+11AB) → 한(U+D55C)
이 방식을 사용하면 NFD 방식보다 텍스트의 사이즈는 작아지게 된다. 하지만, 옛 한글 자모의 결합으로 이루어진 한글 음절 코드가 없으므로 이 음절은 Canonical Composition 하지 못하므로 자소가 분리된 체로 저장하게 된다. 이로 인해, 현대 한글과 옛 한글이 다른 방식으로 저장되므로 텍스트를 처리할 때 유의해야 한다.
NFC는 많은 GNU/Linux 시스템, Windows에서 주로 사용한다.
해결방법
Java에서는 아래처럼 유니코드 정규화 기능을 지원하고 있다. 따라서 필요한 값 혹은 모든 사용자 입력값을 Unicode 정규화를 사용하여 해결할 수 있을 것이다.
import java.text.Normalizer
fun String.normalizeToNfc(): String {
return if (!Normalizer.isNormalized(this, Normalizer.Form.NFC)) {
Normalizer.normalize(this, Normalizer.Form.NFC)
} else {
this
}
}
fun String.normalizeToNfd(): String {
return Normalizer.normalize(this, Normalizer.Form.NFD)
}
평소에 문자 집합과 문자열 인코딩을 알고 있었다면, 위와 같은 인코딩 관련 문제가 발생했을 때 쉽고 빠르게 해결할 수 있었을 것이다. 문자가 깨지거나 보이지 않은 것이 운영 체재, 개발 환경이 달라서인지, 다른 서비스나 라이브러리가 맞지 않아서인지 파악할 수 있을 것이다.
문자 집합과 인코딩
엄격하게 구분해보면 문자 집합과 문자열 인코딩이라는 용어를 구분하여 함께 사용한다.
문자 집합
컴퓨터에서 문자를 나타내기 위해 사용할 수 있는 문자들의 집합을 의미한다.
- ASCII, Unicode, ISO-8859-1 등이 문자 집합에 해당된다.
문자열 인코딩
문자를 코드로 표현하는 방식을 의미한다.
- 문자 인코딩 방식으로는 UTF-8, UTF-16, UTF-32 등이 있다.
ASCII
아스키 코드는 처음으로 표준을 정립한 문자열 인코딩 방식이다. 사용할 수 있는 문자의 종류에는 대문자, 소문자, 아라비아 숫자, 공백 및 특수 문자들이 있으면 문자를 표현할 때는 0부터 127까지, 총 128개의 숫자를 사용한다.
아스키코드는 영어를 제외한 다른 언어를 표현할 수 없다. 그래서 각 나라에서 컴퓨터를 사용하기 시작했을 때는 아스키코드 대신 독자적인 문자 집합과 인코딩 방식을 만들어 사용했다.
EUC-KR(CP949)
한글을 표현하는 방법으로 EUC-KR 문자 집합을 만들었다. EUC-KR은 한국 산업 표준으로 지정된 한국어 문자 집합으로 문자 하나를 표현하기 위해 2바이트를 사용한다. 단, 아스키코드 문자를 표현할 때는 1바이트를 사용하기 때문에 아스키코드와 호환된다.
EUC-KR은 모든 글자가 완성된 형태로만 존재하는 완성형 코드이다. 따라서 초성, 중성, 종성을 조합해 문자를 만들 수 없기 때문에 표현할 수 없는 한글이 일부 존재한다. 유니코드 2.0 버전에서 초성, 중성, 종성에 해당하는 코드로 나눠 표현하는 조합형 글자를 만들면 표현할 수 없는 글자들을 만들 수 있다.
따라서 EUC-KR로 영문자 ‘Hello’와 한글 ‘안녕하세요’ 문자열은 실제 문자열 길이와 버퍼 길이가 다르다. 아스키코드 영역에 있는 글자를 표현할 때는 1바이트를 사용하지만, 한글 문자를 표현할 때는 2바이트를 사용하기 때문이다.
CP949는 EUC-KR을 확장한 문자 집합으로 EUC-KR과 같은 문자열 인코딩이나, 더 많은 문자를 표현할 수 있다. EUC-KR로 표기하더라도 실제는 CP949 문자 집합을 사용하는 경우가 많다.
유니코드
과거에는 EUC-KR처럼 국가별로 독자적인 문자 집합과 인코딩 방식을 사용했다. 따라서 전 세계 사용자를 대상으로 하는 프로그램이나 웹 페이지를 만들려면 언어별로 다른 인코딩 방식을 사용해야 했다.
이런 문제를 해결하기 위해 ISO에서 동일한 규칙으로 모든 언어를 표현할 수 있는 유니코드 문자 집합을 만들었다. 최초 버전인 1.0은 1991년에 제정됐고, 현재 최신 버전은 2024년 9월 12일에 발표된 16.0이다.
유니코드 문자 집합을 표현하는 문자열 인코딩은 총 세 가지로 UTF-8, UTF-16, UTF-32가 있다, 아스키코드나 EUC-KR처럼 문자 집합에 해당하는 하나의 인코딩 규칙만 존재하는 것이 아니다. 이 중 ASCII와 호환이 가능하면서 유니코드를 표현할 수 있는 UTF-8 인코딩이 가장 많이 사용된다.
나머지는 이어지는 [유니코드] 유니코드 인코딩 다음글에 다루고자 한다.
참고 출처
- https://d2.naver.com/helloworld/19187
- https://velog.io/@myungjilee/컴퓨터-구조운영체제-컴퓨터에서-문자를-표현하는-방법
- 도서 [학교에서 알려주지 않는 17가지 실무 개발 기술]
'design & development' 카테고리의 다른 글
[유니코드] 유니코드 인코딩 (0) | 2025.01.12 |
---|---|
[만들면서 배우는 클린 아키텍처] 12. 아키텍처 스타일 결정하기 (0) | 2024.11.26 |
[만들면서 배우는 클린 아키텍처] 11. 의식적으로 지름길 사용하기 (1) | 2024.11.25 |
[만들면서 배우는 클린 아키텍처] 10. 아키텍처 경계 강제하기 (1) | 2024.11.24 |
[만들면서 배우는 클린 아키텍처] 9. 애플리케이션 조립하기 (0) | 2024.11.23 |