2008년 11월 24일
객체를 저장하고 읽어보자.
이번 글에서는 객체를 저장하고 로드하는 것에 대해서 이야기 하도록 하겠습니다.
이 이야기를 하기 전에 우리가 java.io 패키지를 사용 했던 것을 잠시 생각해 봅시다.
가장 처음에서는 문자 스트림을 이용한 예지를 가지고 공부 했었습니다.
주로 키보드 입력 받아들이기, 텍스트 파일 읽기/쓰기 입니다.
그리고 이후에 했던 것은 바이트스트림을 이용했습니다.
텍스트 파일이 아닌 바이너리 파일을 복사 또는 텍스트 파일을 바이트스트림을 이용해서 읽기 및 쓰기도 했었습니다.
마지막으로는 네트워크 프로그래밍 때 이용을 했었습니다.
네트워크 프로그래밍에서는 문자/바이트 스트림을 적절하게 이용하면 모두 이용할 수 있다는 것은 알고 있을 거라 생각됩니다.
그럼 이제 본론으로 돌아와 객체 입출력 이야기를 하도록 하겠습니다.
일단 객체를 가지고 입출력을 하기 이전에 문자스트림도 객체를 이용한 입출력이 아닐까 하는 생각을 할 수 있습니다.
문자열을 이용하려면 String 클래스의 레퍼런스 변수를 이용해야 했기 때문이죠.
그럼 이 부분에 대해서 조금 더 알아보고 넘어가도록 합시다.
객체를 입출력 하기 위해서는 가장 먼저 해당 객체의 클래스가 Serializable 인터페이스를 구현해야 객체 입출력을 할 수 있는 가장 기본 조건이 만들어 집니다.(이 인터페이스는 추상메소드가 없기 때문에 implements를 하는 것 자체가 구현이 되는 약간의 특이한 인터페이스라고 볼수 있습니다.)
그럼 String 클래스는 Serializable 인터페이스를 구현 했을까요?
API를 확인 해 보면 구현 하고 있는 것을 쉽게 확인이 가능합니다.
그리고 실제 String 클래스의 소스 파일에서도 구현을 하고 있다는 것을 쉽게 확인이 가능합니다.
String 클래스의 일부 소스 |
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ ... |
그래서 객체입출력을 모르는 상태에서도 문자스트림을 이용하는데 전혀 문제가 없었습니다. 그렇지만 문자스트림을 객체입출력이라 보는 것은 좀 어렵다고 생각 됩니다. File 클래스 또한 Serializable 인터페이스를 구현 하고 있고 각각의 입출력을 하기 위한 문자/바이트 스트림 구조가 완성 돼 있기 때문입니다.
이 점에 대해서 대충 정리를 하자면 이런 식으로 걸고 넘어진다면 각각의 객체의 특징을 모호하게 만든다고 해야 할까요? ㅎㅎ
자바에서는 모든 클래스들은 Object의 자손들인데 여기서 부터 걸고 넘어져야 겠지요.. ㅎㅎ
그럼 객체 입출력을 위한 준비물 클래스를 소개 하도록 하겠습니다.
1. 위에서도 언급을 했던 Serializable 인터페이스를 구현 하는 클래스가 필요합니다.
2. 입출력을 위해서 ObjectInputStream, ObjectOutputStream 클래스가 필요합니다.
3. 문자/바이트 스트림 떄 처럼 Buffered..들을 이용할 수 있으므로 조건적으로 필요할 수 있는 부분입니다.
위에 준비물을 이용해서 우리가 기초적으로 했었던 입출력 예제와 동일하게 이용하면 됩니다.
클래스 사용 예)
ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("data")));
ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(new FileInputStream("data")));
여기 까지는 확실히 익숙한 소스라는 것을 쉽게 확인이 가능합니다.
그럼 다음으로..
이들을 저장 할 떄는 writeObject(객체명); 읽을 때는 readObject(); 메소드를 사용 하면 손쉽게 이용할 수 있습니다.
그런데 여기서 한가지 문제가 발생 되는 것이 존재합니다. 바로 오브젝트로 쓰고 오브젝트로 읽는 다는 점입니다.
객체를 저장 할 떄는 오브젝트 클래스로 저장 하지만 실제 파일은 바이너리 파일이므로 문제가 없습니다.
(위에서 확인을 해보면 바이트스트림을 이용한 것을 알 수 있습니다.)
그러나 파일을 읽을 때는 오브젝트 클래스로 읽어 들이는데 '오브젝트 클래스로 읽은 데이터를 어떻게 처리 할 것인가?'에서 문제를 발생 시키게 되는 것입니다.
SaveData 클래스의 객체를 저장 했다면 다시 SaveData 객체에 넣어줘야 하는 것이지요. 어떻게 해결 해야 할까요?
답은 간단합니다. 오브젝트 클래스로 읽어들였으면 SaveData 클래스로 캐스팅을 해주면 됩니다.
이점에 대해서는 Vector 클래스를 이용 할 때와 비슷하다고 생각하면 이해하는데 도움이 될 것입니다.
그럼 전체 소스를 보도록 합시다.
import java.io.*;
// 입출력에 사용 될 클래스
class SaveData implements Serializable {
// 맴버변수
private String name;
private String url;
private int i;
private int j;
// 기본 생성자
public SaveData() {
this.name = "만성피로";
this.url = "http://maydaisy.egloos.com/";
this.i = 100;
this.j = 1000;
}
// 오버로딩을 이용한 또 다른 생성자
public SaveData(String name, String url, int i, int j) {
this.name = name;
this.url = url;
this.i = i;
this.j = j;
}
// 출력 메소드
public void print() {
System.out.println(this.name);
System.out.println(this.url);
System.out.println(this.i);
System.out.println(this.j+"\n");
}
}
// 메인 클래스
public class ObjectTest {
// 메인 메소드
public static void main(String[] args) {
SaveData[] data = new SaveData[2];
SaveData[] open = new SaveData[2];
// 각 객체에는 다른 생성자를 이용해서 객체에 연결해준다.
data[0] = new SaveData();
data[1] = new SaveData("인사불성", "???", 10, 20);
try {
// 객체를 저장한다.
ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("data")));
oos.writeObject(data);
oos.flush();
oos.close();
// 저장 파일을 객체로 읽는다.
ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(new FileInputStream("data")));
// 읽어들인 객체는 오브젝트 형이므로 캐스팅을 해줘야 한다.
open = (SaveData[])ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
// 배열객체의 내용을 확인한다.
for(int i=0;i<open.length;i++){
open[i].print();
}
}
}
위와 같은 예제를 이용해서 객체를 저장 한 경우에 이 파일을 텍스트 파일로 읽으면 문자 깨짐 현상이 일어납니다. 당연히 바이너리 파일이기 때문이지요. 그러나 이 파일을 바이너리 코드 편집기를 통해서 열기를 해보면 잼있는 현상을 볼 수 있습니다.
그 예로 위에서 data[0] 객체에 int 형 변수 i에 1000 이라는 숫자를 넣어서 저장을 했습니다. 이것을 바이너리 편집기를 이용해서 열어보면..

이 부분을 수정해서 저장 한다면 당연히 객체를 읽어 오기만 하는 소스를 짜서 파일을 바이너리 편집기로 수정 한 뒤 읽어 들이면 값이 변화된 것을 확인 할 수 있습니다.
이 부분은 게임 세이브 파일을 해킹해서 수정해본 사람이라면 쉽게 이해가 될 것이라고 봅니다.
(파일에 코드가 뒤집힌 경우는 암호화 기법을 이용해서 마지막에 체크썸 코드를 넣는 방식을 취한 경우가 대부분일 겁니다.)
이 점을 생각해 일반적으로 게임에서 세이브를 하는 방식은 객체를 저장하는 방식을 취한다는 것을 예상 할 수 있습니다. 저 또한 그렇게 이용할 것이라고 생각하고 있답니다.
그럼 이번 글은 여기서 마치도록 하겠습니다.
개인적으로 조금 흥미를 유발하는 부분이 많아서 생각 했던 것보다 내용이 좀 길어졌네요 ^^;;
그럼 다음에 또 만나요 ~
# by | 2008/11/24 21:51 | 정리할 자료들 | 트랙백 | 덧글(4)





☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
자바 공부를 정말 열심히 하고 계시는거 같아요 ^^
개인적으로 자바에 관심이 많아서 포스팅 하시는거 가끔씩 보고 갑니다.
부탁인데 미치지만 말아죠= _=ㅋ
애가 점점 미쳐가는거 같애,ㅋㅋㅋㅋ
글에서는 직접적으로 언급하지 않았으나 문자스트림과 바이트 스트림과 같이
객체를 입출력 하는 것 또한 객체 스트림이라고 한답니다 ^^;;