1. Java의 바이너리 직렬화
바이너리 직렬화는 객체를 최대한 작고 빠르게 바이트 스트림으로 바꾸는 Java의 표준 메커니즘입니다. 이를 위해 ObjectOutputStream과 ObjectInputStream 클래스를 사용합니다. 결과 파일은 사람이 읽도록 만들어진 것이 아닌 바이트의 집합입니다.
이것을 바이너리라고 부르는 이유는 모든 것이 ‘원(raw)’ 형태로 직렬화되기 때문입니다: 숫자, 문자열, 배열, 심지어 객체 간의 참조까지 바이트로 변환됩니다. 빽빽하게 짐을 싼 여행가방과 비슷합니다 — 효율적이고 빠르지만, 설명서가 없으면 무엇이 어디에 있는지 알기 어렵습니다.
Java에서는 어떻게 동작할까?
예제로 User 클래스를 만들어 봅시다:
import java.io.Serializable;
public class User implements Serializable {
private String name;
private int age;
// 생성자, 게터/세터
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
바이너리 파일로 직렬화
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
User user = new User("Vasya", 30);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
out.writeObject(user);
System.out.println("User 객체가 user.bin 파일로 직렬화되었습니다");
} catch (Exception e) {
e.printStackTrace();
}
바이너리 파일에서 역직렬화
import java.io.FileInputStream;
import java.io.ObjectInputStream;
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
User loadedUser = (User) in.readObject();
System.out.println("파일에서 읽음: " + loadedUser.getName() + ", " + loadedUser.getAge());
} catch (Exception e) {
e.printStackTrace();
}
주의: user.bin 파일을 텍스트 편집기로 열면 ¬í sr ... 같은 내용이 보일 것입니다 — 정상이며 의도된 동작입니다!
바이너리 직렬화의 장점
- 컴팩트함과 속도. 불필요한 ‘장식’ 없이 최대한 빠르게 저장하고 읽어들입니다.
- 포함된 객체까지 모든 필드가 저장됩니다(그들 역시 Serializable을 구현한 경우).
- 내부 캐시나 Java 프로그램 간 전송에 사용하기 쉽습니다.
단점
- 사람이 읽을 수 없음. 사람이 내용을 들여다보고 이해하기 어렵습니다.
- 클래스 버전에 강하게 묶임. 구조를 변경(필드 추가/삭제)하면 예전 파일을 읽는 것이 ‘깨질’ 수 있습니다.
- 서로 다른 Java 및 JVM 버전 간 호환성 문제.
- 다른 프로그래밍 언어와의 교환에 부적합합니다.
- 보안: 검증되지 않은 소스의 데이터를 역직렬화하면 취약점으로 이어질 수 있습니다.
2. 텍스트 직렬화 형식: JSON, XML 등
바이너리 직렬화는 내부 용도에 좋지만, 종종 서로 다른 언어들(Java, JavaScript, Python) 간에 데이터를 주고받거나 사람이 읽기 쉬운 형태로 저장해야 합니다 — 설정, 로그, API에 편리합니다. 이를 위해 JSON, XML, YAML, CSV 등의 텍스트 형식을 사용합니다.
JSON — 가장 널리 쓰임
JSON (JavaScript Object Notation)은 컴팩트하고 사람이 읽기 쉬운 형식입니다. 직렬화된 User 객체 예:
{
"name": "Vasya",
"age": 30
}
Java에서는 JSON 작업에 보통 Jackson(가장 인기 있음), Gson, 그리고 Moshi, JSON-B 등을 사용합니다.
XML — 프로그래머의 오래된 친구
XML (Extensible Markup Language)은 더 장황하지만, 형식적이고 엄격합니다.
<User>
<name>Vasya</name>
<age>30</age>
</User>
Java에서 XML에는 표준 라이브러리 JAXB(또는 더 오래된 XStream)를 자주 사용합니다.
YAML, CSV 등
- YAML — JSON과 유사하지만 더 간결합니다; 복잡한 객체 직렬화보다는 설정 파일에 더 자주 사용됩니다.
- CSV — ‘평평한’ 테이블에는 좋지만, 중첩 구조에는 적합하지 않습니다.
- 형식은 다양하지만, Java에서는 대개 JSON과 XML이 가장 많이 사용됩니다.
3. 형식 비교: 언제 무엇을 쓸까?
| 형식 | 가독성 | 크기 효율 | 속도 | 호환성 | 사용 시점 |
|---|---|---|---|---|---|
| 바이너리 | 아니오 | ++ | ++ | Java 전용 | 내부 캐시, JVM 간 빠른 저장 |
| JSON | 예 | + | + | 모든 언어 | REST API, 외부 서비스와의 교환, 설정 |
| XML | 예 | - | - | 모든 언어 | 통합, 엄격한 스키마, 레거시 시스템 |
- 바이너리 — 외부 시스템과의 교환이 필요 없고 최대 성능이 중요할 때 내부 용도로 선택하세요.
- JSON — 웹 애플리케이션, 모바일 클라이언트, REST API와의 교환 및 설정 저장에 가장 적합합니다.
- XML — 엄격한 스키마가 필요하거나 ‘기업용’ 통합 시에 적합합니다.
중요! 바이너리 직렬화는 Java 프로그램 간 데이터 전송에만 적합하며, 그마저도 같은 버전끼리 사용할 때가 더 안전합니다. JSON과 XML 같은 텍스트 형식은 더 보편적입니다: 서로 다른 언어와 플랫폼 간 데이터 교환에 적합하고, 데이터를 읽기 쉽고 이식 가능하게 만듭니다.
4. 실습: 바이너리와 텍스트 형식으로 직렬화
바이너리 직렬화 (ObjectOutputStream/ObjectInputStream)
위에서 이미 봤지만, 복습 차원에서 다시 보겠습니다:
// 직렬화
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
out.writeObject(user);
}
// 역직렬화
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
User loadedUser = (User) in.readObject();
}
Jackson으로 JSON 직렬화(요약)
Jackson을 사용하려면 라이브러리를 프로젝트에 추가해야 합니다. 나중에 Maven과 Gradle을 학습하겠지만, 지금은 JAR 파일을 수동으로 추가해도 됩니다. Maven 의존성 예:
<!-- Maven -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
직렬화/역직렬화 예:
import com.fasterxml.jackson.databind.ObjectMapper;
User user = new User("Vasya", 30);
ObjectMapper mapper = new ObjectMapper();
try {
// 문자열로 직렬화
String json = mapper.writeValueAsString(user);
System.out.println(json); // {"name":"Vasya","age":30}
// 파일로 직렬화
mapper.writeValue(new File("user.json"), user);
// 문자열에서 역직렬화
User loadedUser = mapper.readValue(json, User.class);
// 파일에서 역직렬화
User loadedFromFile = mapper.readValue(new File("user.json"), User.class);
} catch (Exception e) {
e.printStackTrace();
}
JSON 파일은 어떤 텍스트 편집기로도 열 수 있어, 데이터를 다양한 애플리케이션과 언어 간에 쉽게 읽고 옮길 수 있습니다.
5. 어떤 형식을 언제 사용할까: 실전 조언
- 내부 캐싱, 임시 파일, Java 프로그램 간 빠른 읽기/쓰기: 표준 바이너리 직렬화를 사용하세요. 다만 버전 호환성에 유의하세요!
- 외부 서비스와의 교환, 설정 저장, 프런트엔드와의 통합: JSON(Jackson, Gson)을 사용하세요.
- 엄격한 스키마가 필요한 ‘기업용’ 시스템 통합: XML(JAXB).
- 사람이 파일을 열어 읽을 수 있어야 할 때: JSON 또는 XML을 사용하고, 바이너리 형식은 피하세요.
6. 직렬화 형식 작업 시 흔한 실수
오류 № 1: 직렬화할 수 없는 필드가 있는 객체를 직렬화하려고 함. 클래스에 Serializable을 구현하지 않은 필드(예: 스트림이나 DB 연결)가 있으면 바이너리 직렬화는 오류를 냅니다. JSON에서는 덜 치명적이지만, ‘비표준’ 타입에서는 역시 문제가 생길 수 있습니다.
오류 № 2: 바이너리 파일을 텍스트 편집기로 열고 놀람. 정상입니다! 바이너리 파일은 사람이 읽도록 만들어진 것이 아닙니다.
오류 № 3: 클래스 구조를 변경했더니 예전 바이너리 파일이 더 이상 읽히지 않음. 바이너리 직렬화는 클래스 구조 변경에 민감하여 InvalidClassException이 자주 발생합니다. JSON/XML에서는 덜 치명적입니다: 알 수 없는 필드는 보통 무시되거나 기본값을 받습니다.
오류 № 4: 외부 시스템과의 교환에 바이너리 직렬화를 사용함. 작동하지 않습니다: 바이너리 형식은 Java 전용이며, 그마저도 버전이 일치해야 합니다.
오류 № 5: JSON/XML에서 필요한 애너테이션을 추가하지 않음. 일부 라이브러리는 @JsonProperty, @XmlElement 같은 애너테이션을 요구하며, 그렇지 않으면 직렬화/역직렬화가 기대와 다르게 동작할 수 있습니다.
오류 № 6: 모든 포함 객체가 직렬화 가능한지 확인하지 않음. 바이너리 직렬화에서 흔한 문제이며, 모델에 복잡한 타입이 있으면 JSON에서도 문제가 될 수 있습니다.
GO TO FULL VERSION