일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 2024 정보처리기사 시나공 필기
- 스프링 mvc1 - 스프링 mvc
- 자바의 정석 기초편 ch6
- 스프링 db1 - 스프링과 문제 해결
- 자바 기본편 - 다형성
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch9
- jpa 활용2 - api 개발 고급
- jpa - 객체지향 쿼리 언어
- 자바 중급1편 - 날짜와 시간
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch11
- 자바 중급2편 - 컬렉션 프레임워크
- 스프링 입문(무료)
- 자바의 정석 기초편 ch4
- 2024 정보처리기사 수제비 실기
- 스프링 mvc1 - 서블릿
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch12
- 코드로 시작하는 자바 첫걸음
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch7
- @Aspect
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch13
- 자바 고급2편 - io
- 자바의 정석 기초편 ch1
- 스프링 mvc2 - 타임리프
- Today
- Total
나구리의 개발공부기록
문자 인코딩, 프로젝트 환경 구성, 컴퓨터와 데이터, 컴퓨터와 문자 인코딩, 문자 집합 조회, 문자 인코딩 예제 본문
문자 인코딩, 프로젝트 환경 구성, 컴퓨터와 데이터, 컴퓨터와 문자 인코딩, 문자 집합 조회, 문자 인코딩 예제
소소한나구리 2025. 2. 20. 12:38출처 : 인프런 - 김영한의 실전 자바 - 고급2편 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
프로젝트 환경 구성
프로젝트 생성
- Name: java-adv2
- Build system: IntelliJ
- JDK: 21 이상
컴퓨터와 데이터
메모리
개발자가 개발하며 다루는 데이터는 크게 010101로 되어있는 바이너리 데이터(byte 기반의 데이터)와 "ABC", "가나다"와 같은 문자로 되어 있는 텍스트 데이터 두 가지임
텍스트 데이터가 어떤 원리를 사용하여 만들어지는지 제대로 이해하지 못하면 실무에서 한글 글자가 이상하게 깨져서 나올 때 근본적인 원인을 찾아서 해결하기 어려움
실무에서 생각보다 많은 개발자들이 텍스트 데이터와 문자 인코딩의 원리를 정확히 알고 있지 않아서 이런 문제를 만났을 때 많이 고생하기도 함
컴퓨터의 메모리는 반도체로 만들어져 있는데 트랜지스터라고 불리는 아주 작은 전자 스위치들이 모여있음
각 트랜지스터는 전기가 흐르거나 흐르지 않는 두 가지 상태를 가질 수 있어서 이를 통해 0과 1이라는 이진수를 표현함
이 트랜지스터들이 모여서 메모리를 구성하는데 흔히 말하는 RAM(Random Access Memory)은 이런 방식으로 만들어진 메모리의 한 종류임
컴퓨터가 정보를 저장하거나 처리할 때 이 트랜지스터들을 켜고 끄는 방식으로 데이터를 기록하고 읽어 들이는데 이 과정은 매우 빠르게 일어나며 현대의 컴퓨터 메모리는 초당 수십억 번의 데이터 접근을 처리할 수 있음
2진수
트랜지스터를 켜고 끈다는 것은 0과 1만 나타낼 수 있는 2진수로 표현할 수 있음
숫자 0을 메모리에 저장한다면 메모리의 트랜지스터를 하나 끄면 되고 숫자 1을 저장한다면 하나 켜면 되며 숫자 2나 3을 표현하려면 트랜지스터를 하나 더 사용하면 됨
핵심은 컴퓨터는 사람 처럼 10진수 숫자를 이해하고 숫자를 메모리에 저장하거나 불러오는 것이 아니라 단지 트랜지스터의 상태만 변경하거나 확인할 뿐임
이렇게 트랜지스터 1개로 2가지만 표현할 수 있는 것을 1비트(bit)라고 하며 1bit를 추가할 때마다 표현할 수 있는 숫자는 2배씩 늘어남
- 1bit: 2가지 표현, (0 ~ 1)
- 2bit: 4가지 표현 (0 ~ 3)
- 3bit: 8가지 표현 (0 ~ 7)
- 4bit: 16가지 표현 (0 ~ 15)
트랜지스터 8개를 함께 묶어서 사용하면 총 256가지 상태를 표현할 수 있고 숫자 0 ~ 255를 표현할 수 있으며 8bit = 1byte라고 함
10진수 100을 메모리에 저장한다면 컴퓨터는 10진수 100을 2진수인 1100100으로 변경하여 저장함
음수를 표현해야 한다면 처음 1bit를 음수, 양수를 표현하는 데 사용함
8bit를 예시로 보면 0과 양수만 표현하는 경우 0 ~ 255를 표현할 수 있지만 음수 표현이 필요한 경우 처음 1bit를 음수와 양수를 구분하는 데 사용하기 때문에 나머지 7bit로 숫자 범위를 사용하여 -128 ~ 127까지 표현할 수 있음
컴퓨터와 문자 인코딩
인코딩, 디코딩
간단한 수학 공식을 사용하면 사람이 사용하는 10진수를 컴퓨터가 사용하는 2진수로 쉽게 변경할 수 있기 때문에 컴퓨터는 10진수를 2진수로 변경해서 메모리에 저장할 수 있음
그러나 숫자가 아닌 문자를 메모리에 저장하려고 하면 컴퓨터는 트랜지스터를 켜고 끄는 2진수만 알고 있기 때문에 문자를 2진수로 변경하는 공식이 필요한데 이런 것은 세상에 없음
이런 문제를 해결하기 위해 초창기 컴퓨터 과학자들은 문자 집합을 만들고 각 문자에 숫자를 연결시키는 방법을 생각해 냈음
예를 들어 문자'A'를 저장하면 컴퓨터는 문자 집합을 통해 'A'의 숫자 값 65를 찾고 65를 메모리에 2진수로 변환해서 저장함
메모리에 저장된 문자를 불러올 때는 반대로 작동하여 메모리에 저장된 2진수 값을 불러와서 10진수로 변환하고 문자 집합을 통해 문자 'A'를 찾아서 화면에 출력함
- 문자 인코딩: 문자 집합을 통해 문자를 숫자로 변환하는 것
- 문자 디코딩: 문자 집합을 통해 숫자를 문자로 변환하는 것
ASCII 문자 집합
각 컴퓨터 회사가 독자적인 문자 집합을 사용한다면 서로 다른 컴퓨터 간에 문자가 올바르게 표시되지 않는 문제가 발생할 수 있으므로 호환성 문제를 해결하기 위해 ASCII(American Standard Code for information Interchange)라는 표준 문자 집합이 1960년도에 개발되었음
초기 컴퓨터는 주로 영문 알파벳, 숫자, 키보드의 특수문자, 스페이스, 엔터와 같은 기본적인 문자만 표현하면 충분했으므로 7비트를 사용하여 총 128가지 문자를 표현할 수 있는 ASCII 공식 문자 집합이 만들어졌음
제어 문자 (0-31, 127), 출력 가능한 문자(32-126)
십진수 | 문자 | 설명 | 십진수 | 문자 | 설명 |
0 | NUL | Null문자 | ... | # $ % & ' ( ) * + , - . / | 특수문자 |
1 | SOH | 헤더 시작 | 48-57 | 0-9 | 숫자 |
2 | STX | 텍스트 시작 | 58-62 | : ; < = > | 특수 문자 |
3 | ETX | 텍스트 끝 | 63 | ? | 물음표 |
4 | EOT | 전송 끝 | 64 | @ | 앳 기호 |
27 | ESC | Escape | 65-90 | A-Z | 대문자 |
32 | 공백 | 97-122 | a-z | 소문자 | |
33 | ! | 느낌표 | 126 | ~ | 틸드 |
34 | " | 큰타옴표 | 127 | DEL | 삭제 |
- ASCII의 숫자는 10진수 숫자가 아니라 문자로 표현됨
- 쉽게 이야기하면 String 타입에 들어있는 "1", "12"처럼 숫자처럼 보이는 문자라고 이해하면 됨
ISO_8859_1
서유럽을 중심으로 컴퓨터용 사용 인구가 늘어나면서, 서유럽 문자를 표현하는 문자 집합이 필요해졌음
ISO_8859_1
- 1980년도 개발
- 기존 ASCII에 서유럽 문자의 추가가 필요해져 국제 표준화 기구에서 서유럽 문자를 추가한 새로운 문자 규격을 만듦
- ISO_8859_1, LATIN1, ISO-LATIN-1 등으로 불림
- 8bit(1byte) 문자 집합으로 총 256가지 표현 가능하며 기존 7비트인 ASCII를 그대로 유지하고 128가지 문자를 추가(주로 서유럽 문자, 특수 문자들)했기 때문에 기존 ASCII 문자 집합과 호환이 가능함
한글 문자 집합
한국에도 컴퓨터 사용 인구가 늘어나면서, 한글을 표현할 수 있는 문자 집합이 필요해졌음
EUC-KR
- 1980년도 개발
- 초창기 등장한 한글 문자 집합(더 이전에 KS5601이 있었음)
- 모든 한글을 담는 것보다는 자주 사용하는 한글 2350개만 포함해서 만들었음
- 한글의 글자는 아주 많기 때문에 256가지만 표현할 수 있는 1byte로 표현하는 것은 불가능하여 2byte(16bit)를 사용하면 총 65536가지를 표현할 수 있어 2byte를 사용함
- ASCII + 자주 사용하는 한글 2350개 + 한국에서 자주 사용하는 기타 글자(한국에서 주로 사용하는 한자 4,888개와 일본어 가타카나 등 포함)가 포함됨
- ASCII를 사용하면 1byte, 한글을 사용하면 2byte를 메모리에 저장하기 때문에 기존 ASCII문자 집합과 호환이 가능함
MS949
- 1990년도에 마이크로소프트가 EUC-KR을 확장하여 만든 인코딩
- 한글, 초성, 중성, 종성 모두 조합하면 가능한 한글의 수는 총 11,172자이므로 2350개만 포함해서 만든 EUC-KR은 "쀍, 삡"과 같이 드물게 사용하는 음절을 표현하지 못하였음
- 기존 EUC-KR과 호환을 이루면서 한글 11,172자를 모두 수용하도록 만든 것이 MS949 임
- EUC-KR과 마찬가지로 ASCII는 1byte, 한글은 2byte를 사용하기 때문에 기존 ASCII 문자 집합과 호환이 가능하였고 윈도우 시스템에서 계속 사용되고 있음
전 세계 문자 집합
전 세계적으로 컴퓨터 인구가 늘어나면서 전세계 문자를 대부분 다 표현할 수 있는 문자 집합이 필요해졌음
문제
- EUC-KR이나 MS949 같은 한글 문자표를 PC에 설치하지 않으면 다른 나라 사람들은 한글로 작성된 문서를 열어 볼 수 없음
- 우리도 마찬가지로 히브리어, 아랍어를 보려면 각 나라의 문자표가 필요함
- 1980년대 말 다양한 문자 인코딩 표준이 존재했지만 이들은 모두 특정 언어 또는 문자 세트를 대상으로 했기 때문에 국제적으로 호환성 문제가 많았음
유니코드의 등장
- 이를 해결하기 위해 전 세계의 모든 문자들을 단일 문자 세트로 표현할 수 있는 유니코드(Unicode) 표준이 1990년대에 도입됨
- 하나의 문자 세트에 전 세계 대부분의 언어를 넣어서 유니코드(Universal) 즉, 전 세계적인 코드라는 뜻으로 전 세계의 모든 문자와 기호를 하나의 표준으로 통합하여 표현할 수 있는 문자 집합을 만든 것이 UTF-16, UTF-8 임
- 두 표준이 비슷하게 등장하였고 초반에는 UTF-16이 인기였음
UTF-16
- 1990년도에 등장하였고 16bit(2byte) 기반으로 만들어짐
- 자주 사용하는 다국어들은 65536가지를 표현할 수 있는 2byte로 표현되었고 영어, 유럽 언어, 한국어, 중국어, 일본어 등이 대상임
- 그 외는 4byte로 표현되었으며 42억 가지를 표현할 수 있었고 고대 문자, 이모지, 중국어 확장 한자 등이 대상임
- 단점: ASCII 영문도 2byte를 사용하여 ASCII와 호환되지 않음
- UTF-16을 사용하면 영문의 경우 다른 문자 집합 보다 2배의 메모리를 더 사용하는데 웹에 있는 문서의 80% 이상은 영문 문서이므로 용량 문제가 생김
- ASCII와 호환되지 않으므로 기존 ASCII코드로 작성된 문서를 UTF-16으로 열 수가 없었음
- 초반에는 UTF-16이 인기여서 이 시기에 등장한 자바도 언어 내부적으로 문자를 표현할 때 UTF-16을 사용하였고 그래서 자바의 char 타입이 2byte를 사용함
- 대부분의 문자를 2byte로 처리하기 때문에 계산이 편리하다는 장점이 있음
UTF-8
- 1990년도에 등장했으며 8bit(1byte) 기반의 가변길이 인코딩이 적용됨
- 1byte ~ 4byte를 사용하여 문자를 인코딩함
- 1byte: ASCII, 영문, 기본 라틴 문자
- 2byte: 그리스어, 히브리어, 라틴 확장 문자
- 3byte: 한글, 한자, 일본어
- 4byte: 이모지, 고대문자 등
- 단점
- UTF-16은 대부분의 기본 문자들이 2바이트로 표현되기 때문에, 문자열의 특정 문자에 접근하거나 문자 수를 세는 작업이 상대적으로 간단한 반면, UTF-8에서는 각 문자가 가변 길이로 인코딩 되므로 이런 작업이 더 복잡함
- ASCII를 제외한 일부 언어에서 더 많은 용량을 사용하여 UTF-16에서 2바이트를 사용하던 한글, 한자, 아랍어 같은 문자들이 UTF-8에서 3바이트 또는 4바이트를 차이함
- 장점: ASCII 문자를 1바이트로 표현하여 ASCII와 호환이 됨
- 현대의 사실상 표준 인코딩 기술임
- 1990년도 후반 ~ 2000년도 초반에 인터넷과 웹이 빠르게 성장하면서 확대되었고 2008년 W3C 웹 표준에 UTF-8을 채택함
- 현재 대부분의 웹사이트와 애플리케이션에서 기본 인코딩으로 사용됨
정리
UTF-8이 현대의 사실상 표준 인코딩 기술이 된 이유
- 저장 공간 절약과 네트워크 효율성
- UTF-8은 ASCII 문자를 포함한 많은 서양 언어의 문자에 대해 1바이트를 사용하는 반면 UTF-16은 최소 2바이트를 사용하므로 주로 ASCII 문자로 이루어진 영문 텍스트에서는 UTF-8이 2배 더 효율적임
- 특히 데이터를 네트워크로 전달할 때는 매우 큰 효율의 차이를 보이며 웹에 있는 문서의 80% 이상은 영문 문서이기 때문에 더욱 효율적임
- ASCII와의 호환성
- UTF-8로 인코딩 된 텍스트에서 ASCII 범위에 있는 문자는 기존 ASCII와 동일한 방식으로 처리되어 호환됨
- 수많은 ASCII 기반으로 구축되어 있는 레거시 시스템과 아무 문제 없이 호환되는 것이 매우 큰 장점임
- 결론: UTF-8을 사용하면 됨
- 한글 윈도우의 경우 기존 윈도우와 호환성 때문에 기본 인코딩을 MS949로 유지하고 있지만 한글 윈도우의 기본 인코딩을 UTF-8로 변경하려고 노력 중이라고 함
문자 집합 조회
사용 가능한 문자 집합 조회
AvailableCharsetsMain
실행 결과가 너무 길기 때문에 실행결과는 생략
package charset;
public class AvailableCharsetsMain {
public static void main(String[] args) {
// 이용 가능한 모든 Charset 자바 + OS
SortedMap<String, Charset> charsets = Charset.availableCharsets();
for (String charsetName : charsets.keySet()) {
System.out.println("charsetName = " + charsetName);
}
System.out.println("========");
// 문자로 조회(대소문자 구분하지 않음), MS949, ms949, x-windows-949
Charset charset1 = Charset.forName("MS949");
System.out.println("charset1 = " + charset1);
// 별칭 조회
Set<String> aliases = charset1.aliases();
for (String alias : aliases) {
System.out.println("alias = " + alias);
}
// UTF-8 문자로 조회
Charset charset2 = Charset.forName("utf-8");
System.out.println("charset2 = " + charset2);
// UTF-8 상수로 조회
Charset charset3 = StandardCharsets.UTF_8;
System.out.println("charset3 = " + charset3);
// 시스템의 기본 Charset 조회
Charset defaultCharset = Charset.defaultCharset();
System.out.println("defaultCharset = " + defaultCharset);
}
}
이용 가능한 모든 문자 집합 조회
Charset.availableCharsets()를 사용하면 이용 가능한 모든 문자 집합을 조회할 수 있음
여기에는 자바가 기본으로 제공하는 문자 집합과 OS가 제공하는 문자 집합을 포함함
Charset.forName()
특정 문자 집합을 지정해서 찾을 때는 Charset.forName(...)을 사용하면 되며 인자로 문자 집합의 이름이나 별칭을 사용하면 되는데 대소문자는 구분하지 않음
별칭은 aliases() 메서드를 사용하면 구할 수 있음
StandardCharsets.UTF_8
자주 사용하는 문자 집합은 StandardCharsets에 상수로 지정되어 있음
public final class StandardCharsets {
public static final Charset US_ASCII = sun.nio.cs.US_ASCII.INSTANCE;
public static final Charset ISO_8859_1 = sun.nio.cs.ISO_8859_1.INSTANCE;
public static final Charset UTF_8 = sun.nio.cs.UTF_8.INSTANCE;
public static final Charset UTF_16BE = new sun.nio.cs.UTF_16BE();
public static final Charset UTF_16LE = new sun.nio.cs.UTF_16LE();
public static final Charset UTF_16 = new sun.nio.cs.UTF_16();
}
Charset.defaultCharset()
현재 시스템에서 사용하는 기본 문자 집합을 반환함
문자를 컴퓨터에 저장하는 등의 작업을 하려면 항상 문자 집합을 지정해주어야 하는데 생략한다면 보통 시스템에서 사용하는 기본 문자 집합을 사용하여 동작함
문자 인코딩 예제
인코딩 예제
EncodingMain1
package charset;
public class EncodingMain1 {
private static final Charset EUC_KR = Charset.forName("euc-kr");
private static final Charset MS_949 = Charset.forName("ms949");
public static void main(String[] args) {
System.out.println("== ASCII 영문 처리 ==");
encoding("A", US_ASCII);
encoding("A", ISO_8859_1);
encoding("A", EUC_KR);
encoding("A", MS_949);
encoding("A", UTF_8);
encoding("A", UTF_16BE);
System.out.println("== 한글 지원 ==");
encoding("가", EUC_KR);
encoding("가", MS_949);
encoding("가", UTF_8);
encoding("가", UTF_16BE);
}
private static void encoding(String text, Charset charset) {
byte[] bytes = text.getBytes(charset);
System.out.printf("%s -> [%s] 인코딩 -> %s %sbyte\n",
text, charset, Arrays.toString(bytes), bytes.length);
}
}
/* 실행 결과
== ASCII 영문 처리 ==
A -> [US-ASCII] 인코딩 -> [65] 1byte
A -> [ISO-8859-1] 인코딩 -> [65] 1byte
A -> [EUC-KR] 인코딩 -> [65] 1byte
A -> [x-windows-949] 인코딩 -> [65] 1byte
A -> [UTF-8] 인코딩 -> [65] 1byte
A -> [UTF-16BE] 인코딩 -> [0, 65] 2byte
== 한글 지원 ==
가 -> [EUC-KR] 인코딩 -> [-80, -95] 2byte
가 -> [x-windows-949] 인코딩 -> [-80, -95] 2byte
가 -> [UTF-8] 인코딩 -> [-22, -80, -128] 3byte
가 -> [UTF-16BE] 인코딩 -> [-84, 0] 2byte
*/
문자를 컴퓨터가 이해할 수 있는 숫자(byte)로 변경하는 것을 문자 인코딩이라 함
String.getByte(Charset) 메서드를 사용하면 String 문자를 byte 배열로 변경할 수 있는데, 이때 중요한 점은 문자를 byte로 변경하려면 어떤 문자 집합을 참고해서 byte로 변경해야 할지 정해서 인자로 Charset 객체를 전달해야 함
문자 집합을 지정하지 않으면 현재 시스템에서 사용하는 기본 문자 집합을 인코딩에 사용함
영문
- US-ASCII, ISO-8859-1, EUC-KR, MS949, UTF-8은 모두 ASCII와 호환됨
- 영문 A가 1byte만 사용되고 숫자 65로 인코딩 됨
- UTF-16은 ASCII와 호환되지 않음
- 영문 A가 2byte를 사용하고 숫자 [0, 65]로 인코딩 됨
한글
- EUC-KR, MS949는 한글 인코딩에 2byte를 사용하고 같은 값으로 인코딩함
- UTF-8은 한글 인코딩에 3Byte를 사용함
- UTF-16은 한글 인코딩에 2byte를 사용함
** 참고
UTF_16, UTF_16BE, UTF_16LE가 있는데 우리는 UTF-16BE를 사용하면 됨
BE, LE는 byte의 순서의 차이일뿐이며 이제 UTF_16을 잘 사용하지 않고 UTF_8은 이런 이슈가 없으므로 참고만 하면 됨
- UTF_16BE: [-84, 0]
- UTF_16LE: [0, -84]
- UTF_16: 인코딩한 문자가 BE, LE 중에 어떤 것인지 알려주는 2byte가 앞에 추가로 붙음
byte 출력에 마이너스 숫자가 보이는 이유
이 내용은 크게 중요한 내용은 아니므로 참고만 해도 충분함
- byte의 기본개념
- 1byte는 8개의 bit로 구성되며 256가지 경우를 표현할 수 있음
- 한글 '가'의 EUC-KR인코딩
- '가'는 EUC-KR에서 2byte로 표현됨
- 첫 번째 byte: 10110000 -> 10진수로 176
- 두 번째 byte: 10100001 -> 10진수로 161
- 따라서 '가'는 10 진수로 표현하면 [176, 161]로 표현되어야 함
- 자바에서의 byte 표현
- 자바의 byte 타입은 양수와 음수를 모두 표현할 수 있는데, 첫 번째 비트(bit)가 0이면 양수, 1이면 음수로 간주됨
- 즉, 첫 비트가 0이면 양수로 간주되며 나머지 7bit로부터 0 ~ 127까지 128가지 숫자를 표현하고, 첫 비트가 1이면 음수로 간주되어 -128 ~ -1까지 128가지 숫자를 표현함
- 결국 자바의 byte는 256가지 값을 표현하지만 표현 가능한 숫자의 범위는 -128 ~ 127 임
- '가'의 EUC-KR 인코딩을 자바 byte로 표현
- 첫 번째 byte 10110000, 10진수로 126: 첫 bit가 1이므로 음수로 해석되며 자바에서는 -80으로 표현됨
- 두 번째 byte 10100001, 10진수로 161: 첫 비트가 1이므로 음수로 해석되며 자바에서는 -95로 표현됨
- 자바에서 음수로 표현할 때는 2의 보수라는 계산 공식이 사용됨
- ex) 10000000 - 110000 = 십진수로 80
정리
- 자바의 byte를 사용해도 실제 메모리에 저장되는 값은 동일하며 단지 자바의 byte 타입이 첫 비트를 음수로 표현하기 때문에 화면에 보여지는 10진수 숫자만 다를 뿐임
- 실제 개발 단계에서는 중간에 byte값을 더하거나 빼면서 변경하는 계산을 거의 하지 않기 때문에 참고만 하고 넘어가도 충분함
문자 인코딩, 디코딩 예제
package charset;
public class EncodingMain2 {
private static final Charset EUC_KR = Charset.forName("euc-kr");
private static final Charset MS_949 = Charset.forName("ms949");
public static void main(String[] args) {
System.out.println("== 영문 ASCII 인코딩 ==");
test("A", US_ASCII, US_ASCII);
test("A", US_ASCII, ISO_8859_1); // ASCII 확장 (LATIN-1)
test("A", US_ASCII, EUC_KR); // ASCII 포함
test("A", US_ASCII, MS_949); // ASCII 포함
test("A", US_ASCII, UTF_8); // ASCII 포함
test("A", US_ASCII, UTF_16BE); // UTF_16 디코딩 실패
System.out.println("== 한글 인코딩 - 기본 ==");
test("가", US_ASCII, US_ASCII); // X
test("가", ISO_8859_1, ISO_8859_1); // X
test("가", EUC_KR, EUC_KR);
test("가", MS_949, MS_949);
test("가", UTF_8, UTF_8);
test("가", UTF_16BE, UTF_16BE);
System.out.println("== 한글 인코딩 - 복잡한 문자 ==");
test("쀍", EUC_KR, EUC_KR); // X
test("쀍", MS_949, MS_949);
test("쀍", UTF_8, UTF_8);
test("쀍", UTF_16BE, UTF_16BE);
System.out.println("== 한글 인코딩 - 디코딩이 다른 경우 ==");
test("가", EUC_KR, MS_949);
test("쀍", MS_949, EUC_KR); // 인코딩 가능, 디코딩 X
test("가", EUC_KR, UTF_8); // X
test("가", MS_949, UTF_8); // X
test("가", UTF_8, MS_949); // X
System.out.println("== 영문 인코딩 - 디코딩이 다른 경우 ==");
test("A", EUC_KR, UTF_8);
test("A", MS_949, UTF_8);
test("A", UTF_8, MS_949);
test("A", UTF_8, UTF_16BE); // X
}
private static void test(String text, Charset encodingCharset, Charset decodingCharset) {
byte[] encoded = text.getBytes(encodingCharset);
String decoded = new String(encoded, decodingCharset); // 인자의 문자 집합으로 디코딩
System.out.printf("%s -> [%s] 인코딩 -> %s %sbyte -> [%s] 디코딩 -> %s\n",
text, encodingCharset, Arrays.toString(encoded), encoded.length,
decodingCharset, decoded);
}
}
/* 실행 결과
== 영문 ASCII 인코딩 ==
A -> [US-ASCII] 인코딩 -> [65] 1byte -> [US-ASCII] 디코딩 -> A
A -> [US-ASCII] 인코딩 -> [65] 1byte -> [ISO-8859-1] 디코딩 -> A
A -> [US-ASCII] 인코딩 -> [65] 1byte -> [EUC-KR] 디코딩 -> A
A -> [US-ASCII] 인코딩 -> [65] 1byte -> [x-windows-949] 디코딩 -> A
A -> [US-ASCII] 인코딩 -> [65] 1byte -> [UTF-8] 디코딩 -> A
A -> [US-ASCII] 인코딩 -> [65] 1byte -> [UTF-16BE] 디코딩 -> �
== 한글 인코딩 - 기본 ==
가 -> [US-ASCII] 인코딩 -> [63] 1byte -> [US-ASCII] 디코딩 -> ?
가 -> [ISO-8859-1] 인코딩 -> [63] 1byte -> [ISO-8859-1] 디코딩 -> ?
가 -> [EUC-KR] 인코딩 -> [-80, -95] 2byte -> [EUC-KR] 디코딩 -> 가
가 -> [x-windows-949] 인코딩 -> [-80, -95] 2byte -> [x-windows-949] 디코딩 -> 가
가 -> [UTF-8] 인코딩 -> [-22, -80, -128] 3byte -> [UTF-8] 디코딩 -> 가
가 -> [UTF-16BE] 인코딩 -> [-84, 0] 2byte -> [UTF-16BE] 디코딩 -> 가
== 한글 인코딩 - 복잡한 문자 ==
쀍 -> [EUC-KR] 인코딩 -> [63] 1byte -> [EUC-KR] 디코딩 -> ?
쀍 -> [x-windows-949] 인코딩 -> [-105, -51] 2byte -> [x-windows-949] 디코딩 -> 쀍
쀍 -> [UTF-8] 인코딩 -> [-20, -128, -115] 3byte -> [UTF-8] 디코딩 -> 쀍
쀍 -> [UTF-16BE] 인코딩 -> [-64, 13] 2byte -> [UTF-16BE] 디코딩 -> 쀍
== 한글 인코딩 - 디코딩이 다른 경우 ==
가 -> [EUC-KR] 인코딩 -> [-80, -95] 2byte -> [x-windows-949] 디코딩 -> 가
쀍 -> [x-windows-949] 인코딩 -> [-105, -51] 2byte -> [EUC-KR] 디코딩 -> ��
가 -> [EUC-KR] 인코딩 -> [-80, -95] 2byte -> [UTF-8] 디코딩 -> ��
가 -> [x-windows-949] 인코딩 -> [-80, -95] 2byte -> [UTF-8] 디코딩 -> ��
가 -> [UTF-8] 인코딩 -> [-22, -80, -128] 3byte -> [x-windows-949] 디코딩 -> 媛�
== 영문 인코딩 - 디코딩이 다른 경우 ==
A -> [EUC-KR] 인코딩 -> [65] 1byte -> [UTF-8] 디코딩 -> A
A -> [x-windows-949] 인코딩 -> [65] 1byte -> [UTF-8] 디코딩 -> A
A -> [UTF-8] 인코딩 -> [65] 1byte -> [x-windows-949] 디코딩 -> A
A -> [UTF-8] 인코딩 -> [65] 1byte -> [UTF-16BE] 디코딩 -> �
*/
영문 ASCII 인코딩
- 영문 'A'를 인코딩하면 1byte를 사용하고 숫자 65가 됨
- 숫자 65를 디코딩하면 UTF-16을 제외하고 모두 디코딩이 가능함
- UTF-16의 경우 디코딩에 실패해서 � 라는 특수 문자가 출력됨
- ASCII는 UTF-16을 제외한 대부분의 문자 집합에 호환됨
한글 인코딩 - 기본
- 한글 '가'는 ASCII, ISO-8859-1로 인코딩할 수 없음
- 이 경우 숫자 63로 인코딩이 되는데 63은 ASCII로 ?라는 뜻으로 모르는 이상한 문자가 인코딩이 되었다는 뜻에서 ?로 인코딩됨
- EUC-KR, MS949, UTF-8, UTF-16은 한글 인코딩, 디코딩이 잘 수행되며 나머지는 2byte, UTF-8은 3byte인 것도 확인할 수 있음
- 한글 '가'는 EUC-KR, MS949 모두 같은 값을 반환하므로 서로 호환됨
한글 인코딩 - 복잡한 문자
- EUC-KR은 자주 사용하는 한글 2350개만 표현할 수 있으므로 '쀍'과 같은 문자 집합에 없는 문자는 인코딩할 수 없음
- 다른 MS949, UTF-8, UTF-16은 모든 한글을 표현할 수 있으므로 인코딩이 가능함
한글 인코딩 - 디코딩이 다른 경우
- '가'와 같이 자주 사용하는 한글은 EUC-KR, MS949 서로 호환되므로 인코딩, 디코딩할 수 있음
- 그러나 EUC-KR의 문자 집합에 없는 한글의 경우는 MS949로 인코딩은 가능하지만 EUC-KR로 디코딩할 수 없음
- UTF-8과 EUC-KR, MS949는 서로 3byte, 2byte로 호환되지 않음
영문 인코딩 - 디코딩이 다른 경우
- ASCII(1byte)에 포함되는 영문은 UTF-16(2byte)을 제외한 대부분의 문자 집합에서 호환됨
정리
- ASCII 영문 인코딩: UTF-16을 제외하고 모두 호환됨
- 사실상 표준인 UTF-8을 사용해야 함
한글이 깨지는 가장 큰 2가지 이유
- EUC-KR(MS949), UTF-8이 서로 호환되지 않음
- 한글이 깨지는 대부분의 문제는 UTF-8로 인코딩한 한글을 EUC-KR(MS949)로 디코딩하거나 EUC-KR(MS949)로 인코딩한 한글을 UTF-8로 디코딩을 할 때 발생
- EUC-KR(MS949) 또는 UTF-8로 인코딩한 한글을 ISO-8859-1로 디코딩 할 때
- 한글을 지원하지 않는 ISO-8859-1로 디코딩을 할 때 발생