본문 바로가기
Java

JAVA Encoding 과 Decoding에 대한 정리

by nomore7 2013. 11. 14.

자바 프로그래밍 특히 웹과 관련된 작업을 할때, 인코딩관련하여 생각보다 복잡한 문제에 직면하게된다.


그런데 워낙 인코딩 할 수 있는 문자조합이 여러가지이다 보니, 처음에 해결하려다가 나중에는 마구잡이로 이것저것 될때까지


입력해보는 경우가 많았다.


그래서 이런 문제에 대해 좀 더 생각해 보았다.


오해1


new String(byte[] bytes,Charset charset)에 대한 오해


String a = new String("한글".getBytes("EUC-KR"),"UTF-8");

이런식으로 사용하게되는 오류를 많이 하고 정확한 문제를 잘몰랐었다.




설명대로 2번째 매개변수의 charset 으로 1번째 매개변수의 바이트배열을 디코딩한다고 되어있다. 

2번째 매개변수로 디코딩하려면 1번째가 해당  charset 으로 인코딩되어있어야 원하는 문자를 출력할 수 있다.


String a = new String("한글".getBytes("EUC-KR"),"UTF-8");

System.out.println(a);

당연히 "한글"이라고 나오지 않는것이 정상이다.



String a = new String("한글".getBytes("EUC-KR"),"EUC-KR");

System.out.println(a);

String b = new String("한글".getBytes("UTF-8"),"UTF-8");

System.out.println(b);

마찬가지로 당연히 "한글"이라고 나오는것이 정상이다.


사실 문제는 다른 Charset을 설정한 매체와의 설정이다.


예를 들어, 이런경우가 있다.

Database의 테이블의 Charset이 latin1로 되어있을 경우, 특별한 설정없이 한글을 입력한다면 입력이 불가능하다.

latin1 의 코드포인트에 한글을 수용할 수 없기 때문이다.


latin1의 경우 1byte(8bit = 128)로서 한글을 128이내에 코드포인트에 없기때문에 표기할 수가 없다.

이런 경우 인코딩형식을 변경하여 저장할 수 있다.


예를 들어 JAVA  소스상에서 (default charset에 따라 다르겠지만,UTF-8로 가정) UTF-8일 경우 latin1 db에 아래와 같은 쿼리는 한글이 저장될 수 없다.

insert into member values("아이디","비밀번호");

각각 "아","이","디","비","밀","번","호" 의 경우 latin1의 코드포인트로 존재하지 않고, 그로인해 ? 로 저장되게 된다.

이는 charset이 일시적으로 맞지 않아서 ? 로 보이는 것이아니라 실제 데이터가 손실된 것이다. 이럴 경우 charset을 아무리 바꿔보아도 복원도 할 수 없다.

이러한 데이터를 select한 후 아무리 JAVA인코딩을 변경해보아도 아무런 성과도 얻지못하는 것은 이러한 이유이다.


이럴때 쿼리를 

insert into member values(new String("아이디".getBytes("EUC-KR")."latin1"),new String("비밀번호".getBytes("EUC-KR")."latin1"));

처럼 한다면, 실제로 깨진 글자가 들어간 것처럼 보이지만 깨져있는 보이는 문자들은 실제로 latin1의 코드포인트에 해당하는 값이 들어잇기 때문에 복원이 가능하다.


Charset.defaultCharset(); // 으로 default charset  확인가능


하지만 인코딩 한 방식과 디코딩한 방식이 일치해야 한다는 것이 

암호화와 복호화를 의미하는 것은 아니다.


예시


public static void main(String args []){

String [] types = {"UTF-8","EUC-KR","ISO-8859-1"};

String testValue = "한글";

System.out.println("기본 글자 : " + testValue);

String encode_result = null;

//TEST1//

System.out.println("------TEST 1------");

try {

for(String type : types){

encode_result = URLEncoder.encode(testValue, type);

System.out.println("encode with " + type +" : "+ URLEncoder.encode(testValue, type));

for(String type2 : types){

System.out.println("decode with " + type2 +" : "+ URLDecoder.decode(encode_result, type2));

}

System.out.println("--------------------");

}

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}

}

이러한 예시코드에서 결과값은 아래와 같습니다.


기본 글자 : 한글

------TEST 1------

encode with UTF-8 : %ED%95%9C%EA%B8%80

decode with UTF-8 : 한글

decode with EUC-KR : ???

decode with ISO-8859-1 : ????¸?

--------------------

encode with EUC-KR : %C7%D1%B1%DB

decode with UTF-8 : ???

decode with EUC-KR : 한글

decode with ISO-8859-1 : ??±?

--------------------

encode with ISO-8859-1 : %3F%3F

decode with UTF-8 : ??

decode with EUC-KR : ??

decode with ISO-8859-1 : ??

--------------------


'한글' 이라는글자를 UTF-8 과 EUC-KR로 인코딩 후 디코딩한 결과는 정상적으로

확인가능하지만 ISO-8859-1 의 경우 확인이 불가능했다.



사실 진짜 문제는 위와같은 경우들과 달리 다른 시스템에서 넘어온 문자들이나,

DB연동을 하는 과정에서 이미 다른 charset으로 인코딩 되어있는 경우이다.


아래는 한글을 저장해야 하는 latin1 DB를 사용하는 경우 자바와의 인코딩 문제.


예를 들어 아래와 같은 테이블이있다.


mysql> show create table test_korean;

+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+

| Table       | Create Table                                                                                                                                            |

+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+

| test_korean | CREATE TABLE `test_korean` (

  `user_name` varchar(64) default NULL,

  `user_real_name` varchar(64) default NULL

) ENGINE=MyISAM DEFAULT CHARSET=latin1

+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+

1 row in set (0.00 sec)


latin1으로 설정된 table 이기때문에 euckr이나 utf8 charset으로 한글을 입력하면 정상적으로 저장할 수없다.

euckr이나 utf8에서 한글의 코드포인트는 latin1의 범위를 넘기 때문이다.

그러므로 한글이나 한자등을 byte변환한후 해당 charset에 맞게 입력해야한다.(위와같은 테이블은 latin1로)

SQL update 해결예시

update test_korean set user_real_name = '"+new String ( "한글".getBytes(CharsetString), "latin1")+"' where user_name = 'test2'


getBytes함수에 사용할 인코딩 방식은 중요하지 않다. 한글을 인식할수 있는 방식으로 디코드한후 latin1로 인코딩하여 저장한다.

DB에서는 이를 다시 latin1로 디코드하고 해당 한글을 확인할 인코딩방식으로 인코딩해야 추후 확인 할 수 있다.


SQL select 해결예시

temp_user_real_name = rs.getString("user_real_name");

temp_user_real_name = new String(temp_user_real_name.getBytes("latin1"),CharsetString);




결국 요지는 데이터가 인코딩,디코딩 과정중 손실되지 않았는지만 확인할 수 있다면, 과정을 따라가며 원하는 한글을 출력할 수 있었다.