반응형
낮에 파일에서 읽은 인코딩과 데이터베이스로 보내는 스트림의 인코딩이 달라서 고생을 좀 했습니다. 편법을 쓰면 금방 해결하는 일이었지만, 쩝... 오기가 생긴다거나 제대로 해야겠다는 것도 아니지만...^^; (어차피 모든 것을 다 알수는 없으니까요)
아무튼, 그동안 뒷전으로 미루었던 것들에 대해 처음부터 다시 한다는 기분으로 살펴보는 것도 좋겠다 싶더군요. 그래서 일단 코드를 짜봅니다.
먼저 파일 네 개를 인코딩이 전부 다르게 해서 만들었습니다. 데이터는 별 뜻 없이 넣었구요.
one, two, three and to the four
1234%^&*(||+)
거추장스러운 허식을 벗고 나면 무엇이 남을까?
난로는 그냥 쓰시고, 동아일보는 봄부터
1234%^&*(||+)
거추장스러운 허식을 벗고 나면 무엇이 남을까?
난로는 그냥 쓰시고, 동아일보는 봄부터
영어, 숫자, 특수문자와 한글을 혼용해서 넣었는데, 생각해보니 ISO8859-1에 한글을 넣은 것은 좀 어처구니 없는 것 같기도 하지만, 일단 똑같이 데이터를 넣고 아래 코드를 실행해 봤습니다.
// 1. ms949, ISO-8859-1, UTF-8, UTF-16 포맷의 파일에서 읽은 문자열의 길이 비교
// 1.1 각 파일 데이터를 String으로 읽기
String files[] = {"ms949.txt", "iso8859-1.txt", "utf-8.txt", "utf-16.txt"};
String data[] = new String[4];
for(int i = 0; i < files.length; i++){
BufferedReader reader = new BufferedReader(
new FileReader(files[i]));
data[i] = ""; // 초기화 하지 않으면, 반복문에서 null 이라는 글자가 추가됨
String line;
while((line = reader.readLine()) != null)
data[i] += line + "/n";
reader.close();
}
// 1.2 String 데이터 길이 비교
for(int i = 0; i < data.length; i++){
System.out.println(files[i] + " " + data[i].length());
System.out.println(data[i]);
}
// 1.1 각 파일 데이터를 String으로 읽기
String files[] = {"ms949.txt", "iso8859-1.txt", "utf-8.txt", "utf-16.txt"};
String data[] = new String[4];
for(int i = 0; i < files.length; i++){
BufferedReader reader = new BufferedReader(
new FileReader(files[i]));
data[i] = ""; // 초기화 하지 않으면, 반복문에서 null 이라는 글자가 추가됨
String line;
while((line = reader.readLine()) != null)
data[i] += line + "/n";
reader.close();
}
// 1.2 String 데이터 길이 비교
for(int i = 0; i < data.length; i++){
System.out.println(files[i] + " " + data[i].length());
System.out.println(data[i]);
}
일단 콘솔 출력은 다음과 같이 나옵니다. 유니코드는 엽기로 출력됩니다. 브라우저에서 많이들 보셨겠지만 ^^;
유심히 살펴볼 것은 String으로 읽은 값의 길이가 다르다는 점입니다.
ms949.txt 98
one, two, three and to the four/n1234%^&*(||+)/n거추장스러운 허식을 벗고 나면 무엇이 남을까?/n난로는 그냥 쓰시고, 동아일보는 봄부터/n
iso8859-1.txt 98
one, two, three and to the four/n1234%^&*(||+)/n?????? ??? ?? ?? ??? ????/n??? ?? ???, ????? ???/n
utf-8.txt 127
one, two, three and to the four/n1234%^&*(||+)/n嫄곗텛?옣?뒪?윭?슫 ?뿀?떇?쓣 踰쀪퀬 ?굹硫? 臾댁뾿?씠 ?궓?쓣源??/n?궃濡쒕뒗 洹몃깷 ?벐?떆怨?, ?룞?븘?씪蹂대뒗 遊꾨???꽣/n
utf-16.txt 178
??
one, two, three and to the four/n1234%^&*(||+)/n거추장스러운 허식을 벗고 나면 무엇이 남을까?/n난로는 그냥 쓰시고, 동아일보는 봄부터/n
iso8859-1.txt 98
one, two, three and to the four/n1234%^&*(||+)/n?????? ??? ?? ?? ??? ????/n??? ?? ???, ????? ???/n
utf-8.txt 127
one, two, three and to the four/n1234%^&*(||+)/n嫄곗텛?옣?뒪?윭?슫 ?뿀?떇?쓣 踰쀪퀬 ?굹硫? 臾댁뾿?씠 ?궓?쓣源??/n?궃濡쒕뒗 洹몃깷 ?벐?떆怨?, ?룞?븘?씪蹂대뒗 遊꾨???꽣/n
utf-16.txt 178
??
인코딩 | String 길이 | 파일 크기(bytes) |
MS949 | 98 | 131 |
ISO-8859-1 | 98 | 96 |
UTF-8 | 127 | 166 |
UTF-16 | 178 | 194 |
글쎄요.. 인코딩에 대해서 본격적으로 다뤄보기 전에 일단 문제의 발단부터 해결까지의 과정을 먼저 말씀드려야겠네요. 우선, 4메가(4351960) 정도 되는 우편번호 데이터를 넣는 sql 파일이 있습니다. MS949로 인코딩된 sql 파일을 그대로 실행시켜서, 데이터베이스에 넣었더니, 자바 클래스에서 비교를 하니까 다 깨지더군요. 그래서 아예 파일 자체를 UTF-8로 갖고 있으면 편하겠다 싶었죠. ^^;
그래서, MS949 파일의 데이터(insert 문장을 길게 나열한 것이죠.)를 BufferedReader.readLine()올 한 줄씩 읽어서 DataOutputStream.writeUTF() 로 쐈더니만 모든 행의 첫 줄에 입력하지 않는 글자들이 하나씩 붙더군요. ㅡㅡ; API를 다시 보니, 일반, UTF-8이 아니라 modified UTF-8를 썼습니다. 그냥 간단히 살펴보면
'u0001'과
'u007F' 사이의 문자는 다음과 같이 한 바이트로 나타내고,
Bit Values
Byte 1
0
bits 6-0
널 문자인 'u0000'와 'u0080'과
'u07FF'
사이의 문자는 두 바이트로
Bit Values
Byte 1
1
1
0
bits 10-6
Byte 2
1
0
bits 5-0
'u0800'과 'uFFFF'
사이의 문자는 세 바이트로 나타낸다고 합니다.
Bit Values
Byte 1
1
1
1
0
bits 15-12
Byte 2
1
0
bits 11-6
Byte 3
1
0
bits 5-0
여하튼, UTF-8의 결함을 보완하기 위해서 수정한 것이라지만, 앞에 붙는 문자들로 인해서 ANT의 sql 태스크가 실행되지 않았습니다. DBMS에서 SQL 실행이 안되는 것이죠. insert 앞에 이상한 것들이 붙어 있으니까.. 문법 오류가 나죠. ^^;
문제를 어떻게 해결하느냐?
먼저 떠오르는 것은 이클립스를 이용한 꽁수였습니다. MS949 파일을 열어서 CTRL+A 하고 나서 CTRL+C 하고, 다시 UTF-8 포맷의 파일을 열어서 CTRL+V 하는 방법이죠. ^^;
두번째는 일일이 바이트를 인코딩해주는 것입니다. 호기심 좀 채우자고, RFC 같은 것을 읽을 수는 없으니까(너무 길더군요) URLEncoder의 encode() 메소드 소스를 보면 할 수 있을 것 같더군요. 뭐, 시간이 되면... 톰캣에 있는 encode() 메소드 구현이나 java.net.URLEncoder의 구현을 보고, 글을 올리겠습니다. 장담은 못하구요. ^^;
마무리는 꽁수가 아니라, JDK 5.0으로 해결한 이야기를 해드리죠.
꽁수를 써서 처음엔 해결하려고 해도 자꾸 이클립스에서 Heap이 넘쳐나는 에러가 나더군요. ^^;
그래서 다른 방법을 찾았는데 나중에 이 문제는 다시 부딪히게 되었습니다. 그건 나중에 이야기하구요. JDK5.0에서는 PrintWriter 클래스의 생성자 중에서 File과 인코딩 방식을 인자로 넣어주는 것이 있더군요. 그것을 사용하니까 잘 되었습니다. 너무 쉽게...^^; (테스트 코드를 첨부합니다.)
public void testReadByUTF8() throws Exception{
BufferedReader reader = new BufferedReader(
new FileReader("ref/zipcode_mysql6.sql"));
PrintWriter writer = new PrintWriter(
new File("setup/zipcode-mysql-6-data.sql"), "UTF-8");
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
writer.println(line);
}
reader.close();
writer.close();
}
BufferedReader reader = new BufferedReader(
new FileReader("ref/zipcode_mysql6.sql"));
PrintWriter writer = new PrintWriter(
new File("setup/zipcode-mysql-6-data.sql"), "UTF-8");
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
writer.println(line);
}
reader.close();
writer.close();
}
프로그램이 잘 실행되었습니다. sysout(System.out.print) 한 내용도 문제 없이 출력되었구요. 파일에 데이터도 잘 복사되었습니다. Ant에서 sql 태스크를 수행하는데 잘 되다가 돌연 에러가 났습니다. 에러가 난 지점을 확인해보니.. 출력이 되다가 말았더군요. ㅡㅡ;
하필, 47,706건 중에서 100개도 안남긴 충북 청원군 부용면 부강8리의 데이터 삽입 구문이 출력되다 말았습니다. 정확히, 4,874,058 bytes 에서 문제가 발생한거죠. TaskMgr(작업 관리자)의 메모리 확인해보고, VM 옵션으로 힙(Heap) 크기를 크게 줘봐도 변화가 없었습니다. ㅡㅡ;
에러가 나는데, .log는 도대체 어디 있는건지. 탐색기에서 검색으로 찾아봤더니 이클립스의 로그 파일인 .log는 프로젝트 디렉토리 아래의 .metadata 디렉토리에 있었습니다.
java.lang.OutOfMemoryError: Java heap space
이걸로 어쩌란 것인지..ㅡㅡ;
결국은 이클립스 뉴스리스트를 보고 알았습니다. 이클립스에서 프로그램을 실행할 때 VM 옵션을 수정해줘봐야 소용이 없고, eclipse를 실행할 때 VM 옵션을 수정해야 합니다. 이클립스가 실행될 때의 디폴트 힙 사이즈는 128MB인 모양입니다.(정확하지는 않습니다. ^^;)
그런데, 플러긴까지 설치하면 금방 넘어가겠죠. 그랬던 것입니다. 고작 5MB도 안되는 파일을 읽다가 멈췄다기 보다, 딱 4MB 정도 남았는데 제가 파일을 로딩하면서 힙 사이즈를 초과한 것이라고 추측이 됩니다. 처음에 말했던 꽁수를 부릴 때도 에러가 난 것도 마찬가지 이유죠.
eclipse.exe -vm <자바 설치 디렉토리>jrebinjavaw.exe -vmargs -Xmx512M
위와 같이 실행했더니 무사히 앞서 했던 작업을 마칠 수 있었습니다. 이클립스 역시 VM 상에서 돌아간다는 것을 깜빡했던거죠. 이클립스가 수행되는 VM의 힙 사이즈를 늘려서 문제는 해결되었습니다.
저처럼 평소에 더블클릭으로만 이클립스를 띄웠던 분들은 참조하세요.
eclipse [platform options] [-vmargs [Java VM arguments]]
-vmargs args VM에 옵션을 전달하고자 할 때
닫기
퍼온 글
퍼온 글
출처: http://blog.naver.com/inking007/120001550360
문자 코드 변환 과정
1. 컴파일
컴파일시 원시파일의 문자열을 현재 환경(한글환경 KSC5601)의 locale로 인코딩하여 읽어 들인후
유니코드에서의 대응하는 코드값으로 변환한다.
원시파일에서 '한글' 이라는 문자열은 다음과 같은 hex code를 가지고 있다. (KSC5601)
c7 d1 b1 db
2.실제 .class 파일로 저장시 UTF-8로 변환하여 저장한다.
예를 들어 다음과 같은 코드 작성후
컴파일하면
public class Test {
public static void main(String args[]) {
String str = "한글";
System.out.println(str);
}
}
public static void main(String args[]) {
String str = "한글";
System.out.println(str);
}
}
클래스파일 Test.class에 '한글' 이라는 문자열은 다음과 같이 인코딩되어 저장된다.
ED 95 9C EA B8 80
디컴파일 하면 다음과 같이 변환된다. UTF-8 --> UTF-16
String s = "uD55CuAE00";
uD55C '한'
uAE00 '글'
uAE00 '글'
아래 URL을 클릭하면 해당 Unicode가 나타내는 문자및 UTF-8코드를 볼수 있다.
3. 실행시 문자열을 UTF-16으로 변환하여 로드하고 화면에 출력시 디폴트 인코딩으로 출력한다.
닫기
인코딩에 따라 글자수 인식이 달라진다.
인코딩이 되지 않고 한글이 입력될 때는(ISO8859-1로 추정)
'이호'라고 입력하면 6자로 인식된다.
한글 한 글자를 3자로 인식하는 것이다.
'육봉달'이라고 입력하면 9.. 받침하고 상관없다.
'튕뽃뾳'이라고 해도.. 9
이번엔 인코딩을 해본다.
먼저 UTF-8
위의 세 개의 문자를 대상으로 테스트하면
2, 3, 3 글자.. 역시 받침에 상관없고
한글 한 글자를 영문 알파벳 하나와 동일하게 인식한다.
퉧뾳뾳, 111, aaa, %%%
위의 입력 모두 동일하게 3글자로 인식한다.
EUC-KR은 다를까?
위의 네개 문자 조합을 입력하면 나머지 셋은 3 글자로 인식하지만
한글인 '퉧뾳뾳'은 2글자로 인식하여 모두 6글자가 된다.
이것만 보고 EUC-KR은 한글을 두 자로 인식하구나 일반화하는 것은 위험한 일이었다.
다음과 같은 기이한 현상을 보인다.
퉧뾳뾳 : 제대로 인코딩을 못하며 24글자로 인식한다.(퉧뾳뾳)
이쁋: 역시 두 번째 글자는 인식을 제대로 못해 9자가된다.(이쁋)
제대로 인식하지 못하는 글자에 대해서는 8글자가 배당됨을 알 수 있다.
EUC-KR을 관행처럼 쓰고 있는 가운데
UTF-8을 쓰자할 때 뭐가 장점이냐 물으면.. 사실 떠오르는 것이 별로 없었는데
한가지 예로 들 수 있는 현상이다.
UTF-8에선.. 뾳뛕쁋쿅푝 같은 외계어같은 문자마저 올바로 5글자로 인식한다는 면에서 EUC-KR보다 실용성이 높다고 할 수는 없지만 신뢰성이 높은 것은 분명한 것이다.
출처 : http://blog.iampro.net/2368
출처 : http://blog.iampro.net/2368