2. 컴퓨터 이야기/프로그래밍

자바를 이용하여 텍스트 파일을 읽고 쓰는 방법론

래빗 크리스 2010. 1. 31. 19:41


이곳에서는 원본의 내용이 텍스트인 파일을 읽고 쓰는 방법론을 설명합니다.
텍스트 파일을 읽을때 byte 스트림을 이용하느냐, char 스트림을 이용하느냐, 문자셋을 전환하느냐..
텍스트 파일을 쓸때 byte 스트림을 이용하느냐, char 스트림을 이용하느냐, 문자셋을 전환하느냐..
등의 요소가 조합되면서 자바 클래스로 어떤 것을 사용하느냐가 결정됩니다.
문자셋을 조정하려면 InputStreamReader 와 OutputStreamWriter 가 필요합니다.

파일은 크게 텍스트 파일바이너리 파일로 구분됩니다.
자바에서는 텍스트는 char 로 작업하고 바이너리는 byte 로 작업한다고 생각하세요.
HTML, JSP, ASP, XML 등등은 코드체계와 문자셋만 다를뿐 텍스트 파일이고..
이미지, 동영상, 워드, 엑셀, 파워포인트 등등은 바이너리 파일입니다.
윈도우의 메모장 등등, 텍스트 에디터 프로그램으로 파일을 열었을때,
영어나 한국어 등등 사람이 읽기 편한(?) 언어로 표시가 된다면 텍스트 파일,
보통의 사람이 알 수 없는 이상한(?) 문자들이 표시되어 나온다면 바이너리(이진) 파일입니다.
텍스트 내용의 파일을 바이너리 파일로 변형하면 군더더기(?) 내용이 더 들어가기 때문에 파일이 조금더 커지지만,
바이너리 형식으로 텍스트 파일의 모든 내용을 표현할 수 있기 때문에 내용 자체는 유지가 됩니다.
바이너리 내용의 파일을 텍스트 파일로 변형하면 텍스트로 표현 가능한 내용만 추출되기 때문에 파일이 더 작아지고,
필요한 내용이 없어지므로 파일이 손상됩니다.
다만, 파일의 내용이 텍스트로만 구성되어 있었다면 이 파일을 바이너리에서 텍스트로, 텍스트에서 바이너리로 변환할때,
파일 사이즈는 조금 달라져도 원문의 내용만은 보존이 됩니다.

자바로 프로그래밍 하는 입장에서..
텍스트 파일을 처리할 때는 char[] 를 바이너리 파일을 처리할 때는 byte[] 를 이용하면,
내용의 손실이나 군더더기 없이 정상적인 처리가 가능합니다.
오라클 DB 의 경우 LongRaw 나 BLob, CLob 등의 타입이 있는데,
LongRaw 와 BLob 을 char[] 로 처리하면 문제가 생기지만, CLob 을 byte[] 로 처리해도 문제는 없습니다.
이미지 파일을 char[] 를 이용하여 처리하였다면 나중에 이미지 뷰어나 이미지 에디터에서 읽지 못할 수도 있고,
텍스트 파일을 byte[] 를 이용하여 처리하여도 문제는 없지만, 효율은 떨어집니다.
바이너리 Stream (byte[] 포함) 을 이용하여 파일을 처리했을때 텍스트의 한글이 깨진다면,
문자 스트림으로 전환하고 문자셋을 조정해 주어야 합니다.
바이너리 Stream (byte[] 포함) 으로 한글이 안 깨진다면, OS 와 DB 그리고 WAS 모두 동일한 문자셋을 사용해서 그렇겠죠.
OS 에서 설정된 문자셋, DB에서 설정된 문자셋, WAS(Web Application Server)에서 설정된 문자셋이 서로 다르다면
충분히 고민을 해야 하는 거죠.

버퍼(Buffer) 개념은 디스크의 물리적인 구조 때문에 사용하는데, 버퍼를 이용하면 훨씬 더 좋은 퍼포먼스가 가능합니다.
하드 디스크의 헤더가 데이타를 읽고 쓸때 일정한 크기의 블럭 단위로 작업을 합니다.
OS 에 따라 블럭의 크기가 다르지만 1 KB 의 배수로 사용됩니다. 그래서 파일을 다루면서 버퍼를 1024 바이트로 처리합니다.
OS 가 블럭 단위로 파일을 처리하기 때문에,
블럭 보다 작은 파일을 저장하더라도 해당 블럭의 나머지 공간에 다른 파일의 내용을 기록하지는 못합니다.
모든 파일들의 파일 사이즈를 더했더니 하드디스크 크기보다 작은데도 디스크에 더이상 파일을 저장하지 못하는 것은,
더이상 기록할 블럭이 없기 때문이지요.
그렇다고 블럭의 크기를 마냥 작게만 할 수는 없습니다.
블럭을 관리하는 데 할당된 용량을 초과하는 디스크의 나머지 부분은 사용하지 못하니까요.
그래서 32 비트 OS 로는 사용하지 못하는 공간을 64 비트 OS 로는 사용할 수 있는 것이지요.

자바에서 파일을 다룰때 사용하는 클래스가 굉장히 많고, 설명하는 분들도 이런 클래스 저런 클래스 사용하는데..
텍스트 파일이냐 바이너리 파일이냐, 버퍼를 사용하느냐 사용하지 않느냐,
텍스트 파일에서 문자셋을 관리할 필요가 있느냐 없느냐에 따라 사용하는 클래스가 다릅니다.
물론, 텍스트 내용의 파일을 바이너리로 읽어서 텍스트로 바꿀 수도 있습니다.
InputStreamReader 을 사용하면 됩니다. (Stream 자체는 바이너리)
OuputStreamWriter 는 텍스트를 바이너리로 저장합니다.
InputStreamReader 와 OutputStreamWriter 는 문자셋을 조정할 수 있습니다.



1. 텍스트 파일을 byte 스트림으로 읽어서 (버퍼 No, 문자셋 No),
   byte 스트림으로 쓰기 (버퍼 No, 문자셋 No).


FileInputStream 을 사용하여 파일을 byte 스트림으로 오픈 →
FileInputStream 을 사용하여 오픈된 byte 스트림을 read() →
FileOutputStream 을 사용하여 파일을 byte 스트림으로 오픈 →
FileOutputStream 을 사용하여 오픈된 byte 스트림으로 write() 하고 flush().

파일을 복사할때 많이 사용하는 방법이죠. 읽고 쓰는 두개의 스트림을 열어서 read() write() flush() 하면 끝납니다.
while 문을 사용하여 read(byte[] b, int off, int len) 로 각각을 읽어서 버퍼에 담았다가,
문자열 작업을 한 다음에 다시 write() 할 수도 있습니다. 문자열 조작이 필요한 경우 조금 더 복잡해지는 거죠.
flush() 는 파일을 읽는 스트림에서는 필요 없고, 파일에 쓰는 스트림에서만 필요하겠죠.
메모리에 남아 있을지도 모르는 내용을 남김없이 파일에 저장하려면 꼭 필요합니다.
물론, FileInputStream 으로 파일을 잡아서 추상클래스인 InputStream 으로 던져주고 InputStream 으로 read() 한 다음..
FileOutputStream 으로 파일을 잡아서 추상클래스인 OutputStream 에 주고 OutputStream 으로 write() flush() 해도 되죠.



기본은 이렇지만, 퍼포먼스 향상을 위해서 버퍼를 사용한다면 어떻게 해야 할까요..?



2. 텍스트 파일을 byte 스트림으로 읽어서 (버퍼 Yes, 문자셋 No),
   byte 스트림으로 쓰기 (버퍼 Yes, 문자셋 No).

FileInputStream 을 사용하여 파일을 byte 스트림으로 오픈 →
BufferedInputStream 을 사용하여 byte 스트림을 byte 버퍼에 read() →
FileOutputStream 을 사용하여 파일을 byte 스트림으로 오픈 →
BufferedOutputStream 를 사용하여 byte 버퍼를 오픈된 byte 스트림으로 write() 하고 flush().

물론, 파일 쓰기를 할때 write(byte[] b) 나 write(byte[] b, int off, int len) 중 필요한 것을 선택합니다.
버퍼 클래스를 이용하면 좋은 퍼포먼스를 유지하면서 읽기와 저장이 가능합니다.
아래와 같이 while 문으로 버퍼에 담는 것은 파일을 읽는 쪽에서만 필요하고,
파일을 저장할때는 BufferedOutputStream 이 알아서 해 주므로 while 문이 필요하지 않습니다.
문자셋을 변경할 필요가 없다면 좋은 해결방법이 될 수 있습니다.
while(true){
  byte[] buff = new byte[1024];
  if(bis.read(buff, 0, buff.length)==-1)  break; // bis 는 BufferedIntputStream 으로 정의된 변수
  sb.append(new String(buff));                    // 텍스트 파일을 스트림으로 읽어서 처리할때는
                                                               // byte[] 로 읽은 내용을 String 으로 바꿔서 StringBuffer 에 담는다
}




퍼포먼스는 좋아졌는데, 텍스트 파일에서 한글이 깨진다면 어떻게 해야 할까요..?



3. 텍스트 파일을 byte 스트림으로 읽어서 (버퍼 No, 문자셋 Yes),
   byte 스트림으로 쓰기 (버퍼 No, 문자셋 Yes).

FileInputStream 을 사용하여 파일을 byte 스트림으로 오픈 →
InputStreamReader 를 사용하여 char 스트림으로 문자셋을 바꾸면서 byte 스트림을 read() →
FileOutputStream 을 사용하여 파일을 byte 스트림으로 오픈 →
OutputStreamWriter 를 사용하여 byte 스트림으로 문자셋을 바꾸면서 char 스트림을 write() 하고 flush().

InputStreamReader(InputStream in, "EUC-KR") 과 같이 문자셋을 기술합니다.
OutputStreamWriter(OutputStream out, "EUC-KR") 과 같이 문자셋을 기술합니다.
InputStreamReader 를 추상 클래스인 Reader 에 던져주어 read() 하고,
OutputStreamWriter 를 추상 클래스인 Writer 에 던져주어 write() flush() 해도 됩니다.




문자셋을 변경했지만 버퍼를 사용하지 않았군요. 버퍼를 사용한다면 어떻게 될까요..?



4. 텍스트 파일을 byte 스트림으로 읽어서 (버퍼 Yes, 문자셋 Yes),
   byte 스트림으로 쓰기 (버퍼 Yes, 문자셋 Yes).

FileInputStream
 을 사용하여 파일을 byte 스트림으로 오픈 →
InputStreamReader 를 사용하여 char 스트림으로 문자셋을 바꾸면서 byte 스트림을 전달 →
BufferedReader 을 사용하여 전달된 char 스트림을 char 버퍼에 read() →
FileOutputStream
을 사용하여 파일을 byte 스트림으로 오픈 →
OutputStreamWriter 를 사용하여 byte 스트림으로 문자셋을 바꾸면서 char 스트림을 전달 →
BufferedWriter 를 사용하여 char 버퍼를 전달된 char 스트림으로 write() 하고 flush().

InputStreamReader(InputStream in, "EUC-KR") 과 같이 문자셋을 기술합니다.
OutputStreamWriter(OutputStream out, "EUC-KR") 과 같이 문자셋을 기술합니다.
파일 쓰기를 할때는 write(char[] cbuf, int off, int len) 와 write(String s, int off, int len) 중 필요한 것을 선택합니다.
버퍼 클래스를 이용하면 좋은 퍼포먼스를 유지하면서 읽기와 저장이 가능합니다.
아래와 같이 while 문으로 버퍼에 담는 것은 파일을 읽는 쪽에서만 필요하고,
파일을 저장할때는 BufferedWriter 가 알아서 해 주므로 while 문이 필요하지 않습니다.
문자셋을 변경할 필요가 있다면 유일한 해결방법이 됩니다.
while(true){
  char[] buff = new char[1024];
  if(br.read(buff, 0, buff.length)==-1)  break; // br 은 BufferedReader 로 정의된 변수
  sb.append(buff);                                    // char[] 는 StringBuffer 에서 append 인자로 이용
}





그런데, 바이너리 스트림을 사용하지 않으려면 어떻게 해야 할까요..?



5. 텍스트 파일을 char 스트림으로 읽어서 (버퍼 Default, 문자셋 Default),
   char 스트림으로 쓰기 (버퍼 Default, 문자셋 Default).

FileReader 
를 사용하여 파일을 char 스트림으로 오픈 →
BufferedReader 을 사용하여 오픈된 char 스트림을 char 버퍼에 read() →
FileWriter
를 사용하여 파일을 char 스트림으로 오픈 →
BufferedWriter 를 사용하여 char 버퍼를 오픈된 char 스트림으로 write() 하고 flush().

그러면 문자셋을 조정하려면..? FileReader 가 아니라 FileInputStream 과 FileOutputStream 으로 열어야 합니다.
FileReader 는 InputStreamReader 를 extends 하지만 문자셋을 조정할 수는 없거든요.
JSP 에 설정된 문자셋을 상속받겠죠..? 
기본 문자셋으로 처리가 가능하다면 좋은 처리 방법이 됩니다.



잘못된 내용이나 추가할 내용이 있으면 댓글 남겨 주세요. 본글에 반영하겠습니다. ^^>