Develop

[java] 입출력 스트림 3부 (오브젝트)

by hooni posted Apr 23, 2013
?

단축키

Prev이전 문서

Next다음 문서

ESC닫기

크게 작게 위로 아래로 댓글로 가기 인쇄
Java Input/Ouput Stream #3 (Object)

자바 입출력을 하다 보면 파일을 저장에서 한가지 궁금점이 생기게 됩니다. 
"PC게임에서 사용 되는 저장 기법은 무엇인가?" 라는 생각을 하게 됩니다.

단순히 String, int, long 등의 변수값을 텍스트 형식 또는 바이너리 형식으로 저장하는 형식인가?

그럼 바이너리 형식으로 저장한다면 어떻게 변수 값을 바이너리로 저장해야 하는 것인가?


이런 의문을 갖게 됩니다.
이 궁금증의 해답을 찾으려면 바이트스트림 계층도를 참고 하는 것이 기본적인 힌트 입니다.

보통 입출력 스트림을 이용할 떄는 버퍼라는 것을 달아주는 경우가 있습니다.
이 경우에는 버퍼를 사용하지 않을 때 보다 더 수월한 동작을 보여준다고 합니다.

그래서 블로그에 소개된 입출력 예제를 보면 입출력에 상당히 긴 문장을 갖추고 있습니다.
그렇다면 당연히 각 입출력 스트림 계층도에서 각 클래스들이 무엇인지 파악할 수 있습니다.

그런데 바이트 스트림에서 좀 특이하게 존재하는 것이 있습니다.
Object 단어로 시작하는 ObjectInputStream과 ObjectOutputStream 입니다.

이 두 클래스는 바이트스트림 이면서도 재미난 특징을 갖고 있어서 오브젝트 스트림이라고도 합니다.
간략한 설명을 하자면 오브젝트를 입출력 하기 위한 스트림이기 때문에 객체를 입출력합니다.

지금 까지 입출력 스트림을 보면 "텍스트를 저장한다.", "바이너리 파일을 복사한다." 라고만 생각하게 됐습니다.
그런데 사실은 이 모든 행위가 오브젝트 스트림이라고 할 수 있습니다.

파일을 불러오는 행위를 잘 생각해 봅시다.
실제로 파일을 가져 오는 것이 아니라 File 클래스의 객체 통해서 가져오게 됩니다. 
반대로 텍스트를 저장할 때도 마찬가지 입니다.
텍스트를 저장하는 것이 아니라 String 클래스의 객체를 저장하는 것 입니다.

그렇다면 "개발자가 만든 클래스를 저장할 수 있지 않을까?" 라는 생각도 충분히 할 수 있습니다.
그런데 그런 의심만으로 개발자가 제작한 클래스의 객체를 저장하려고 시도를 하면 저장은 커녕 에러 메시지가 출력되게 됩니다.

무엇인가 2%가 모자르게 되는 것이지요.
여기서 한가지 알아야 할 것이 있습니다.
입출력을 할 수 있는 클래스는 전부 직렬화가 구현이 돼 있어야 한다는 것 입니다.

직렬화는 자바에서 제공하는 클래스들은 대부분 구현이 돼 있습니다.
확인 방법은 자바 API 문서를 이용해서 String 클래스를 확인해 보면 Serializable 인터페이스가 구현 돼 있다고 확인 할 수 있습니다.

실제로 Serializable 인터페이스는 아무 내용도 없는 인터페이스 입니다.
그러나 이 인터페이스를 구현(implements Serializable) 하게 되면 오브젝트 스트림을 비롯한 모든 입출력 스트림을 사용할 수 있게 됩니다.

쉽게 말해 Serializable 인터페이스를 구현이 직렬화를 하는 것 입니다.
직렬화를 하게 되면 여러가지로 좋은 이점이 나오는데 만약 네트워크 프로그래밍을 하는 경우에 직렬화를 하지 못한다면 여러가지 정보를 전달 할 떄 번거로운 코딩 작업을 해야 합니다.
종류가 10개 정도 되는 String, int, long, byte 정보를 전송한다고 생각 하면 일일이 전송을 하도록 출력에 해당하는 코딩을 해줘야 합니다.

그러나 여기에 자료형 클래스를 직렬화 시켜서 네트워크 목적지로 출력 스트림을 만들어 준다면 효율은 아주 좋아집니다. 그리고 처음에 생각 했던 게임에서의 저장 방식을 만들 수 있게 됩니다. 

보통 RPG 게임을 하게 되면 현재 까지 진행한 부분까지 저장을 하고 로드를 하면 그 지점까지 불러와 지게 됩니다.
보통 파일을 저장하는 것은 케릭터 정보와 시나리오 정보를 저장하는 것입니다.
시나리오 정보와 케릭터 정보를 하나의 클래스로 묶어 저장을 한다면 불러올 떄도 아주 쉽습니다.

그럼 간단한 직렬화 예제를 보도록 합시다.
import java.io.*;

class A{
    public static void main(String args[]) {
        SaveFile file = new SaveFile();
        file.str = "직렬화";
  
        try{
            ObjectOutputStream oos =
                new ObjectOutputStream(new FileOutputStream("SaveFile"));
            oos.writeObject(file);
            oos.flush();
   
            ObjectInputStream ois =
                new ObjectInputStream(new FileInputStream("SaveFile"));
            SaveFile file2 = (SaveFile) ois.readObject();
   
            ois.close();
            oos.close();
   
            file2.print();
   
        }catch(Exception e){}
    }
}

class SaveFile implements Serializable{
    String str;
 
    public void print(){
        System.out.println(str);
    }
}

위 예제를 보면 SaveFile 클래스는 직렬화 하지 않으면 저장 및 읽기가 불가능합니다.
그러나 직렬화를 구현함으로써 오브젝트 스트림을 이용해서 저장과 불러오기를 할 수 있습니다.

사용 방법은 기존의 입출력 스트림과 큰 차이는 없지만 주의해야 할 점은 여기도 존재합니다.

직렬화 된 클래스는 조금이라도 편집 되면 편집 이전에 저장된 클래스 파일은 읽어 올 수 없습니다.
(가끔 게임 버전을 업그레이드 하면 세이브 파일 호환 되지 않는 것이 아마 이 문제와 관련 된 것으로 예상 됩니다.)
오브젝트 스트림을 이용해서 읽기를 하는 경우에 읽어 오게 되는 객체는 최상위 클래스인 Object 클래스형이므로 캐스팅을 해야 합니다.
개발자가 만든 클래스는 당연히 존재해야 합니다.
그래야 해당 클래스로 저장한 파일을 읽어 올 수 있습니다.

위 예제는 아주 기본적인 예제 입니다.
연습을 많이 해보시길 바랍니다.
특히 오브젝트 스트림은 입출력 스트림 중에서도 가장 흥미로운 부분이 아닐까 합니다.

이렇게 직렬화 된 객체를 저장 한 뒤에 울트라에디트 같은 바이너리 파일 편집기를 이용해 열어 보면 일부 코드를 수정함으로 실제 파일 내용이 수정 되는 것도 볼 수 있습니다.
(간혹 데이터 파일에 CheckSum 비트를 추가해 암호화 같은 기법을 사용 하는 경우가 있는데 이런 경우에는 데이터 수정이 쉽지는 않습니다.)

직렬화에 관한 공부를 별도로 하지 않아서 자세한 것은 알지 못하지만 직렬화는 데이터의 입출력 방식을 일정한 규칙을 이용해 입출력의 호환성이나 여러가지 효율을 향상 시키는 것으로 예상 됩니다.

감사합니다.

TAG •