금일은 Java 의 문자 인코딩에 관하여 알아보겠습니다.
텍스트 데이터가 어떤 원리로 사용해서 만들어지는지 알지 못한다면 실무에서 한글이 깨지는 현상을 해결 가능하다.
RAM 은 0과 1이라는 이진수를 통해 데이터를 저장하는 메모리의 한 종류입니다.
초당 수십억 번의 데이터 접근을 처리 가능합니다.
10진수의 데이터를 표현하기 위해서는 3울 이진수로 표현할 때 11(2) 로 표현되고 하나의 0 또는 1인 1bit 의 크기를 나타내며 즉 2 bit 이상 사용해야 합니다.
1 bit 가 늘어날수록 표현 가능한 데이터의 수는 2배씩 증가하게 되는 것이다. 다들 아시겠지만 1Byte = 8bit 입니다.
음수를 표현하기 위해서는 처음 1 bit 를 음수와 양수를 구분하는데 사용 하며 나머지 7 bit 로 숫자 범위를 사용합니다.
때문에 8bit 로 표현 가능한 범위는 -127 ~ 128 까지 표현 가능합니다.
숫자가 아닌 문자를 메모리에 저장하기 위해서는 문자 집합(character set) 을 활용해야 합니다.
우리가 흔히 알고 있는 아스키 코드 표를 생각하시면 됩니다.
서로 다른 컴퓨터 간에 올바르게 표시되지 않는 문제를 해결하기위해 호환성 문제를 해결하기 위해 ASCII 라는 표준 문자 집합이 있습니다.
기본적으로 알파벳, 숫자, 키보드의 특수문자, 엔터와 같은 기본적인 7 bit 를 활용하여 총 128가지 문자를 표현할 수 있습니다.
또한 확장된 ASCII 가 있으며 기존 ASCII에 서유럽 문자의 추가 필요 했으며 8 bit 를 사용한 256 가지의 표현이 가능하기도 합니다.
이밖에도 한글 문자 집합이 있으며 16 bit 를 사용하여 표현이 가능하며 영어를 사용하며 1byte, 한글을 사용하면 2byte 를 메모리에 저장하게 됩니다.
EUC-KR이나 MS949 같은 한글 문자표를 PC에 설치하지 않으면 다른 나라 사람들은 한글로 작성된 문서를 열어 볼 수 없습니다.
각 나라의 문자표가 필요했고 호환성 문제를 해결해야 했습니다.
해결방안으로 유니코드, UTF-16, UTF-8 등 전 세계의 모든 문자들을 표현 가능하도록 하였습니다.
8bit(1byte) 기반, 가변길이 인코딩
1byte ~ 4byte를 사용해서 문자를 인코딩
단점: 상대적으로 사용이 복잡함
저장 공간 절약과 네트워크 효율성
ASCII 와의 호환성
2008년 W3C 웹 표준에 UTF-8 채택
현재 대부분의 웹사이트와 애플리케이션에서 기본 인코딩으로 사용
참고: 한글 윈도우의 경우 기존 윈도우와 호환성 때문에 기본 인코딩을 MS949로 유지한다. 한글 윈도우도 기본 인코딩
을 UTF-8로 변경하려고 노력중이다.
문자 집합을 사용해서 문자 인코딩을 사용하는 코드 예제입니다.
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("=====");
// 문자로 조회(대소문자 구분X), 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);
}
코드를 보면 java + Os 에서 사용가능한 Charset, charsetName 의 이름 및 별칭 조회, 문자 및 상수 그리고 기본 Charset 조회 등을 확인 가능합니다.
주요 메서드로는 다음과 같습니다.
Charset.forName() // 특정 문자 집합을 지정해서 찾을 때
StandardCharsets.UTF_8 // 자주 사용하는 집합은 상수로 지정되어 있다.
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() // 현재 시스템에서 사용하는 기본 문자 집합을 반환
Charset(문자 집합) 을 사용해서 실제 문제 인코딩을 해보겠습니다.
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", 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);
}
문자를 컴퓨터가 이해할 수 있는 숫자(byte)로 변경하는 것을 문자 인코딩이라 한다.
String.getByte(Charset) 메서드를 사용하면 String 문자를 byte 배열로 변경할 수 있다.
이때 중요한 점이 있는데, 문자를 byte로 변경하려면 문자 집합이 필요하다는 점이다. String.getByte() 의 인자로 Charset 객체를 전달하여 원하는 문자 집합을 선택하여 byte 로 변환이 가능하다.
영문
US-ASCII, ISO-8859-1, EUC-KR, MS949, UTF-8은 모두 ASCII와 호환된다
한글
EUC-KR, MS949는 한글 인코딩에 2byte를 사용하고 같은 값으로 인코딩한다.
참고 : 자바에서의 byte 표현에서 음수는 메모리에
첫번째 bit에 1로 표현되어 저장되지만
디코딩을 하게되면 음수로 표현된다.
인코딩과 디코딩 그리고 다양한 예시에 대해 알아보겠습니다.
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, 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("가", UTF_8, UTF_8);
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);
}
해당 문자들을 디코딩 할 경우 ? 라는 특수문자가 나오게 되면 실패한다는 의미입니다.
인코딩과 디코딩의 Charset 의 경우의 수 에 따라 test 함수의 결과값이 다른것을 관찰할 수 있습니다.
한글의 인코딩과 - 디코딩이 다를 경우
가 와 같이 자주 사용하는 한글은 EUC-KR, MS949 서로 호환된다
한글을 인코딩할 때 UTF-8과 EUC-KR(MS949)는 서로 호환되지 않는다.
영어의 인코딩과 - 디코딩이 다를 경우
EUC-KR(MS949), UTF-8이 서로 호환되지 않음
EUC-KR(MS949) 또는 UTF-8로 인코딩한 한글을 ISO-8859-1 로 디코딩 할 때