직렬화와 역직렬화
- 직렬화(Serialization) : 컴퓨터 과학의 데이터 스토리지 문맥에서 데이터 구조나 오브젝트 상태를 동일하거나 다른 컴퓨터 환경에 저장하고 나중에 재구성할 수 있는 포맷으로 변환하는 과정
- 역직렬화(Deserialization) : 반대로 일련의 바이트로부터 데이터 구조를 추출하는 과정
직렬화를 사용하는 이유
자바에서 원시타입 bytem short, int, long, float, double, boolean, char가 있습니다. 이를 제외한 객체들은 모두 주소값을 참조하는 참조타입입니다.
원시타입은 Stack영역에 값을 갖고있어 직접 접근이 가능하지만. 참조타입은 Heap에 존재하고 메모리 주소를 참조하고 있어 프로그램이 종료되거나 가비지컬렉터에 의해 Heap 영역에서 데이터가 제거된다면 데이터도 함께 사라집니다. 따라서 이 주소값을 외부에 전송했다고 하더라도 실제 메모리 주소에는 객체가 존재하지 않을 수 있다는것이죠.
따라서 참조타입의 객체들을 원시값 형식으로 데이터를 변환하는 과정을 거쳐 전달해야만 합니다.
직렬화의 형식
형식 | 특징 | |
텍스트 기반 형식 | CSV, JSON, XML | 사람이 읽을수 있는 형태 저장 공간의 효율성이 떨어지고, 파싱하는데 오래걸림 데이터의 양이 적을 때 주로 사용 최근에는 JSON형태로 직렬화를 많이함 모든 시스템에서 사용 가능 |
이진(Binary) 형식 | CBOR, BSON, LEB128, MessagePack, Pickle, Protocol Buffers | 사람이 읽을 수 없는 형태 저장 공간의 효율성이 좋고, 파싱이 빠름 데이터의 양이 많을 때 주로 사용 모든 시스템에서 사용 가능 |
자바 직렬화 | Java 시스템 간의 데이터 교환이 필요할 때 사용 |
자바에서의 직렬화와 역직렬화
1. Serializable 인터페이스 구현
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
2. 직렬화 구현
public static void main(String[] args) {
Person person = new Person("김OO", 20);
byte[] serialized;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(person);
// serialized -> 직렬화된 Person 객체
serialized = baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
// 바이트 배열로 생성된 직렬화 데이터를 base64로 변환
System.out.println(Base64.getEncoder().encodeToString(serialized));
// 출력 결과
// rO0ABXNyAB/sp4HroKztmZTsmYDsl63sp4HroKztmZQuUGVyc29uc1NTeAAdcbkCAAJJAANhZ2VMAARuYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7eHAAAAAUdAAF6rmAT08=
}
3. 역직렬화 구현
역직렬화의 조건은 자바 직렬화 대상 객체가 동일한 serialVersionID를 가지고 있어야합니다.
public static void main(String[] args) {
byte[] serialized = { -84, -19, 0, 5, 115, 114, 0, 31, -20, -89, ... 생략};
try {
ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
ObjectInputStream ois = new ObjectInputStream(bais);
// 역직렬화된 Person 객체를 가져온다
Person newObject = (Person) ois.readObject();
System.out.println("newObject = " + newObject);
// 출력결과
// newObject = Person { name = '김OO', age = 20 }
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
만약 특정 필드의 값을 직렬화하고 싶지 않다면 transient 키워드를 붙히면됩니다.
public class Person implements Serializable {
private transient String name;
private int age;
// 이하 생략
}
// 해당객체의 역직렬화 결과
// Person { name = null, age = 20 }
직렬화의 주의점
1. 객체의 멤버변수가 추가되었을 때
// Caused by: java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = 8310077512291807673, local class serialVersionUID = 2986034573208750977
InvalidClassException 예외가 발생합니다. serialVersionUID가 일치하지 않는다는 뜻입니다.
멤버변수의 추가로인해 serialVersionUID가 새로운 값을 가지면서 이전 serialVersionUID 값과 일치하지 않아 생긴 예외입니다.
public class Person implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return String.format("Person { name = '%s', age = %d }", name, age);
}
}
serialVersionUID를 명시해주면 멤버 변수를 추가해도 에러가 발생하지 않습니다. 그리고 기존에 있던 멤버변수를 삭제해도 에러가 발생하지 않습니다.
2. 객체의 멤버변수의 타입이 변경되었을 때
public class Person implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String name;
private long age;
public Person(String name, long age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return String.format("Person { name = '%s', age = %d }", name, age);
}
}
// java.io.InvalidClassException: Person; incompatible types for field age
age의 타입을 int -> long 으로 변경한다면 InvalidClassException 이 발생합니다.
결론
자바 직렬화는 장점이 많지만 단점도 많습니다. 이 단점을 보완하기 힘든 형태로 되어있기 때문에 제약이 많아 사용시에 주의가 필요합니다.
- 오랫동안 외부에 저장하는 의미 있는 데이터는 직렬화하지 않는다.
- 역직렬화시 반드시 예외가 생긴다는것을 염두한다.
- 자주 변경되는 비즈니스적인 데이터는 자바 직렬화를 사용하지 않는다.
- 긴 만료시간을 가지는 데이터는 JSON 등 다른 포맷을 사용하여 저장한다.
참고
'Language > JAVA' 카테고리의 다른 글
[JAVA] String의 불변성(immutability) (0) | 2024.09.23 |
---|---|
[JAVA] 함수형 인터페이스에 대해서 알아보자 (0) | 2024.09.15 |
[JAVA] Collection List를 최적화해보자 (0) | 2024.03.18 |
[JAVA] 제네릭(Generic) 이란? (0) | 2024.01.10 |
[JAVA] 정규식 Regular Expression (0) | 2024.01.02 |