본문 바로가기
[ JAVA ]/JAVA

[ Java ] 객체 직렬화(Object serialization)

by 환이s 2023. 1. 11.
CHAPTER 22. 객체 직렬화 알아가기

 

오늘은 Java의 직렬화, 역직렬화에 대해서 알아봅시다.

 

■ 직렬화(serialization)란?

 

자바 시스템 내부에서 사용되는 Object 또는 Data를 외부의 자바 시스템에서도 사용할 수 있도록 byte 형태로 데이터를 변환하는 기술입니다. 또한 JVM(Java Virtual Machine)의 메모리에 상주(힙 또는 스택)되어 있는 객체 데이터를 byte 형태로 변환하는 기술이라고도 합니다.

 

그렇다면 직렬화는 왜 배워야 할까??

 

우리는 파일에 텍스트를 기록하고, 이전 데이터를 기록하는 방법은 많이들 알고 계시겠습니다. 그런데 만약 이런 종류의 데이터들이 아니라 객체를 파일로 저장하거나 읽어올 수 있을까요??  직렬화만 배운다면 할 수 있습니다.

 

 

가 ) 직렬화(serialization) 클래스 구현하기

 

직렬화를 하려면 우선 Serializable 인터페이스를 implements 구현해야 합니다.

 

직렬화를 수행할 땐 두가지만 알고 있으면 됩니다.

 

- 직렬화가 가능한 클래스를 구현하기

- 직렬화가 된 클래스의 객체를 쓰고 읽는 Stream 준비하기

 

자 그럼 예제를 통해서 알아봅시다.

 

<예제 1>

//객체 직렬화를 하려면 Serializable 인터페이스를 구현해야 함
//객체 직렬화는 메모리에 저장된 객체를 파일로 저장하거나 네트워크로 전송할 때 사용


import java.io.Serializable;

// MVC패턴에서 쓰이는 용어
// 1) DTO : Data Transfer Object(데이터를 전달하는 객체, 결과물을 저장)
// 2) VO : Value Object, DTO의 개념이지만 생성자 없이 getter,setter로만 구성됨
// 3) DAO : Data Access Object(데이터 조작 객체, DB와 연결해서 데이터를 구하는 비지니스로직단)
public class MemberDTO implements Serializable {
    private String userid;
    private String name;
    private String passwd;
    private int age;
    private String email;
    //생성자 2개, getter/setter , toString()

    public MemberDTO(){}


    public MemberDTO(String userid, String name, String passwd, int age, String email) {
        this.userid = userid;
        this.name = name;
        this.passwd = passwd;
        this.age = age;
        this.email = email;
    }

    public String getUserid() {
        return userid;
    }

    public void setUserid(String userid) {
        this.userid = userid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "MemberDTO{" +
                "userid='" + userid + '\'' +
                ", name='" + name + '\'' +
                ", passwd='" + passwd + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                '}';
    }
}

 

클래스 이름을 MemberDTO로 만들었는데, 여기서 DTO란 데이터를 전달하면서 결과물을 저장해 주는 객체입니다. DTO는 MVC패턴에서 쓰이는 용어로 추후에 Spring 포스팅으로 찾아뵙겠습니다.

 

직렬화에 대해서 이해하기 쉽게 생성자 2개, get/set, toString()을 활용했습니다.

그렇다면 이제 구현해볼까요??

 

<예제 2>

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class MemberUse {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;//메모리에 있는 객체를 파일로 저장시켜주는 객체
        MemberDTO m1 = new MemberDTO("kim", "김길동", "1234", 30, "kim@gmail.com");
        MemberDTO m2 = new MemberDTO("lee", "이길동", "1234", 20, "lee@gmail.com");
        MemberDTO m3 = new MemberDTO("park", "박길동", "1234", 40, "park@gmail.com");

        try {
            //직렬화 : 메모리 -> 프로그램 -> 파일
            fos = new FileOutputStream("c:\\test\\object.dat");
            oos = new ObjectOutputStream(fos);
            oos.writeObject(m1);//m1객체를 파일에 저장
            oos.writeObject(m2);//m2객체를 파일에 저장
            oos.writeObject(m3);//m3객체를 파일에 저장
            System.out.println("객체를 파일에 저장했습니다.");
        } catch (Exception e) {
            e.printStackTrace();
        }
        //역직렬화(deserialization) : 파일 =>프로그램=> 메모리
        FileInputStream fis = null;
        ObjectInputStream ois =null;
        try {
            fis = new FileInputStream("c:\\test\\object.dat");
            ois = new ObjectInputStream(fis);
            MemberDTO dto1 = (MemberDTO)ois.readObject();
            MemberDTO dto2 = (MemberDTO)ois.readObject();
            MemberDTO dto3 = (MemberDTO)ois.readObject();
            System.out.println(dto1);
            System.out.println(dto2);
            System.out.println(dto3);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 

예제 코드들을 보시면 Stream 활용을 많이 하는데, 이전 포스팅을 참고해주시면 좋을 거 같습니다..!!

 

 

직렬화를 좀 더 쉽게 설명하자면  아래와 같습니다.

//직렬화 : 메모리 -> 프로그램 -> 파일

 

그렇다면 출력결과를 확인해 봅시다.

 

위 출력으로는 Output(역직렬화(Deserialization))은 출력이 정상적으로 나왔습니다.

그럼 Input(직렬화(serialization))은 잘 출력되었을까요??

 

위 예제 코드를 구현해 보시면 정상적으로 출력되는 걸 확인할 수 있습니다.

 

나 ) 직렬화(serialization) 장점

 

  •  자바 직렬화는 자바 시스템에서 개발에 최적화되어 있습니다.
  •  복잡한 데이터 구조의 클래스의 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화, 역직렬화가 가능합니다.
  • 데이터 타입이 자동으로 맞춰지기 때문에 역직렬화가 되면 기존 객체처럼 바로 사용이 가능합니다.

그럼 장점에서 말하는 역직렬화는 어떤 의미가 있을까??

 

다 ) 역직렬화(Deserialization)

 

Byte로 변환된 data를 원래대로 Object나 data로 변환하는 기술을 역직렬화(Deserialization)라고 부릅니다. 또한 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태를 의미합니다. 즉, 객체화하여 파일이나 네트워크로 write 할 때 직렬화(serialization)를 거쳐서 전달이 되고, 반대로 읽어올 때 역직렬화(Deserialization)를 거쳐서 가져오게 됩니다.

 

역직렬화(Deserialization)를 수행할 땐 세 가지만 알고 있으면 됩니다.

 

  • 직렬화 대상이 된 객체의 클래스가 클래스 패스에 존재해야 하며 import 되어 있어야 합니다.
  • 중요한 점은 직렬화와 역직렬화를 진행하는 시스템이 서로 다를 수 있다는 것을 반드시 고려해야 합니다.
  • 자바 직렬화 대상 객체는 동일하게 가지고 있어야 합니다.

 

<예제 2> 코드 참고 해보세요!

 

라 ) 렬화(serialization) 사용 시 주의할 점

 

  • 외부 저장소로 저장되는 데이터는 짧은 만료 시간이 아니라면 자바 직렬화 사용을 지양합니다.
  • 역직렬화 시 반드시 예외가 생갈 수 있다는 점을 진지하고 개발해야 합니다.
  • 자주 변경되는 비즈니스적인 데이터에 대해 자바 직렬화 사용을 지양합니다.
  • 긴 만료 시간을 가지는 데이터는 JSON 등 다른 포맷을 사용하여 저장합니다.

 

■ 비순차 접근 파일(RandomAccessFile)

 

이전 포스팅에서 살펴본 I/O Stream을 이용하면 파일에 순차적으로 입출력 작업을 수행할 수 있습니다. 하지만 순차적인 접근이 아닌 임의의 지점에 접근하여 작업을 수행하고 싶다면, RandomAccessFile 클래스를 사용하면 됩니다.

 

RandomAccessFile 클래스는 파일만을 대상으로 하며, 임의의 지점에서 입출력을 동시에 수행할 수 있습니다.

 

RandomAccessFile 클래스의 생성자에는 인수로 파일의 이름뿐만 아니라 파일 모드까지 함께 전달해야 하는데, 파일 모드란 파일의 사용 용도를 나타내는 문자열입니다. 자바에서 사용할 수 있는 대표적인 파일 모드는 다음과 같습니다.

 

파일 모드 설 명
"r" 파일을 오로지 읽는 것만 가능한 모드로 개방합니다.
"w" 파일의 쓰기 전용입니다.
"rw" 파일을 읽고 쓰는 것이 모두 가능한 모드로 개방합니다. 만약 파일이 없으면 새로운 파일을 생성합니다.

 

예제 코드로 알아봅시다.

 

<예제 3> 

import java.io.RandomAccessFile;

public class RandomFile {
    public static void main(String[] args) {
        String str = null;
        try { // r:읽기전용, w:쓰기전용, rw:읽기,쓰기
            RandomAccessFile file = new RandomAccessFile("c:\\test\\rand.txt","rw");
            System.out.println(file.getFilePointer());
            //getFilePointer() : 파일포인터(파일을 어디까지 읽었는지 가리켜 줌)
            file.seek(8);// 8번째 인덱스 부터
            System.out.println(file.getFilePointer());//8
            file.write("HTML".getBytes());
            System.out.println(file.length());//문자길이(한글은 3바이트의 길이값을 가짐)
            System.out.println(file.getFilePointer());
            while (file.getFilePointer() < file.length()){
                // 파일의 내용보다 파일포인터의 위치값이 적으면 반복
                str = file.readLine();
                //한글처리
                //String(문자열,캐릭터셋) 인코딩방식 변환
                //iso-8859-1 , 8859_1 : 서유럽 언어 인코딩 방식
                //ms949(eucKr) : 2바이트(완성형)
                //utf-8 : 3바이트 , 초성(1byte), 중성(1byte), 종성(1byte) - 조합형
                str = new String(str.getBytes("8859_1"),"utf-8");

            }
            System.out.println(file.length());
            System.out.println(file.getFilePointer());
            file.close(); // 파일 저장시점
            System.out.println("파일이 저장되었습니다.");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 

위 코드를 실행 전에 먼저 예제 파일을 생성해서 txt 안에 "Hello!! Java" 문구를 준비했습니다.

 

<예제 3> 코드를 실행해보면 결과적으로 8번 인덱스 자리에 있는 Java 문구를 HTML로 수정되어야 성공입니다.

결과는 아래와 같습니다.


마치며

 

직렬화, 역직렬화할 경우 객체들의 순서가 중요합니다. ArratList 등을 활용하면 손쉽게 할 수 있습니다!

항상 직렬화 가능한 클래스들의 상태를 확인하시는 것도 중요합니다. 

 

728x90

'[ JAVA ] > JAVA' 카테고리의 다른 글

[ Java ] File 클래스  (0) 2023.01.16
[ Java ] Socket Programming  (0) 2023.01.13
[ Java ] I/O Stream  (2) 2023.01.10
[ Java ] GUI 프로그래밍 응용  (2) 2023.01.09
[ Java ] GUI 프로그래밍 개념  (2) 2023.01.07