-
[JAVA ] 직렬화(Serialization)와 역직렬화(Deserialization)프로그래밍 언어/JAVA 2022. 10. 11. 13:47
서블릿에 HttpServletRequest에대해 알아보다 클라이언트에서 서버에 데이터를 전송하는 방법 중 HTTP API를 이용하는 방법이 있다. 이는 HTTP BODY에 메시지를 담아서 요청을 보내는 것인데, JSON 형식을 가장 많이 사용한다고 한다. JSON 형식으로 요청을 보내서 이를 서버에서 helloData.getUsername() 이런식으로 값을 띄워보려고 할때 jackson의 ObjectMapper 클래스를 이용해 json 데이터를 Java 객체로 역직렬화하는 방법을 알게되었다. 그렇다면 직렬화랑 역질렬화가 무엇일까?
📑 직렬화와 역직렬화란?
📌 데이터 직렬화 (java 객체 -> json)
: 메모리를 디스크에 저장하거나, 네트워크 통신에 사용하기 위한 형식으로 변환하는 것이다.
📌 데이터 역직렬화 (json 문자열 -> java 객체)
: 디스크에 저장한 데이터를 읽거나, 네트워크 통신으로 받은 데이터를 메모리에 쓸 수 있도록 변환하는 것이다.
📑 직력화가 필요한 이유
사용하는 데이터의 메모리 구조는 크게 2가지로 나뉜다.
1. 기본형의 값 형식 데이터 (primary)
: int,float, char 등 스택에 메모리가 쌓이고 직접 접근이 가능하다.
2. 참조 형식 데이터(reference)
: 객체와 같은 참조 형식 변수를 선언하면 힙에 메모리가 할당되고, 스택에서는 이 힙 메모리를 참조하는 구조로 되어있다.스택과 힙 메모리영역 차이가 궁금하다면 아래 포스팅 참고!
https://wonisdaily.tistory.com/23
위의 기본형, 참조형 두 가지 데이터 중에서 디스크에 저장하거나 통신할 때는 값 형식 데이터만 사용할 수 있다. (프로그램에 따라 기본 타입의 데이터를 객체로 취급해야 하는 경우가 있을 때 자바에서 Wrapper 클래스를 사용하는 것을 예로 들을 수 있겠다. ) 참조 형식 데이터는 실제 데이터 값이 아닌 힙에 할당되어 있는 메모리 번지 주소를 가지고 있기 때문이다.
예를들어, 객체 A의 주소값이 0x100이라고 가정했을 때, 이 값을 파일에 포함하여 저장했다고 해보자. 이후 프로그램을 종료하고 다시 실행해서 주소값 0x100을 가져오더라도 기존 A객체에 데이터를 가져올 수 없다. why?? 프로그램이 종료되면 기존에 할당되었던 메모리(0x100)은 해제되고 없어지기 때문이다. 네트워크 통신 또한 마찬가지인데, 각 PC마다 사용하고 있는 메모리 공간 주소는 전혀 다르므로 내가 다른 PC로 전송한 A 객체 데이터(0x100)은 무의미하다.
🔨 여기서 직렬화를 왜 사용하는가를 이해하게 되는데,
직렬화를 하게 되면 각 주소 값이 가지는 데이터를 전부 끌어 모아 값 형식 데이터로 변환해 준다. 직렬화가 된 데이터는 언어에 따라서 텍스트 또는 binary 등의 형태가 되는데, 이러한 형태가 되었을 때 저장하거나 통신한다면 파싱이 가능한 유의미한 데이터가 된다. 즉) 직렬화를 사용하는 이유는 사용하고 있는 데이터를 파일 저장 혹은 데이터 통신에서 파싱할 수 있는 유의미한 데이터를 만들기 위함이다.
📑 데이터 직렬화의 종류
📌 CSV, XML, JSON 직렬화
- 사람이 읽을 수 있는 형태
- 저장 공간의 효율성이 떨어지고, 파싱하는 시간이 오래 걸림.
- 데이터의 양이 적을 때 주로 사용
- 최근 JSON을 통해 데이터 직렬화를 많이 한다.
- 모든 시스템에서 사용이 가능하다.
📌 Binary 직렬화
- 사람이 읽을 수 없는 형태
- 저장 공간을 효율적으로 사용할 수 있고 파싱하는 시간이 빠름
- 데이터의 양이 많을 때 주로 사용
- 모든 시스템에서 사용이 가능함
- ex) 프로토콜 버퍼, Apache Avro 등
📌 JAVA 직렬화
- Java 시스템 간의 데이터 교환이 필요할 때 사용함
📑 JAVA의 직렬화와 역직렬화
📌 Java 직렬화
: 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부 자바 시스템에서도 사용할 수 있도록 바이트 형태로 데이터 변환하는 기술이다. JVM의 메모리에 상주(힙 또는 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술이다. 직렬화(스트림에 객체를 출력)에는 ObjectOutputStream을 사용한다.
FileOutputStream fos = new FileOutputStream("objectfile.ser");
ObjectOutputStream out = ObjectOutputStream(fos);
out.writeObject(new UserInfo());우의 코드는 objectfile.ser 이라는 파일에 UserInfo 객체를 직렬화하여 저장한다. 출력할 스트림(FileOutputStream)을 생성해서 이를 기반스트림으로하는 ObjectOutputStream을 생성한다. ObjectOutputStream의 writeObject(Object obj)를 사용해 객체를 출력하면, 객체가 파일에 직렬화되어 저장된다.
📌 Java 역직렬화
: 바이트로 변환된 데이터를 다시 객체로 변환하는 기술이다. 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 기술이다. 역직렬화(스트림으로부터 객체를 입력)에는 ObjectInputStream을 사용한다.
FileInputStream fis = new FileInputStream("objectfile.ser");
ObjectInputStream in = new ObjectInputStream(fis);
UserInfo info = (UserInfo)in.readObject();writeObject대신 readObject를 사용해 저장된 데이터를 읽기만 하면 객체로 역직렬화 된다. 다만 readObject()의 반환 타입이 Object이기 때문에 객체 원래 타입으로 형변환 해주어야 한다.
📑 직렬화 역직렬화 예제로 살펴보기
직렬화가 가능한 클래스를 만든다. 여기서 중요한 점은 implemets로 java.io.Seriaalizable 인터페이스를 구현해야 된다.
package part_15; public class Ex15_19_UserInfo implements java.io.Serializable{ private String name; private String password; private int age; public Ex15_19_UserInfo(){ this("Unknown", "1111", 0); } public Ex15_19_UserInfo(String name, String password, int age) { this.name = name; this.password = password; this.age = age; } public String toString(){ return "(" + name + ", " + password + ", " + age + ")"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
< 직렬화 예제 >
아래 코드에서 생성한 객체를 직렬화하여 파일(UserIfno.ser)에 저장한다. ObjectOutputStream 자체가 텍스트 파일 저장을 위한게 아니라 바이너리 코드로 직렬화 되기에 파일을 살펴보면 외계어로 txt 파일에서 확인 가능하다.
ы sr part_15.Ex15_19_UserInfog뎮?Ot? I ageL namet Ljava/lang/String;L passwordq
만약 이를 사람이 볼 수 있는 데이터로 바꾸고 싶다면 JSON이나 XML 등등 데이터 포맷에 맞게 변경하면 된다.
package part_15; import java.io.*; import java.util.ArrayList; public class Ex15_20_Serializable { public static void main(String[] args) { try{ String fileName = "UserInfo.ser"; FileOutputStream fos = new FileOutputStream(fileName); BufferedOutputStream bos = new BufferedOutputStream(fos); ObjectOutputStream out = new ObjectOutputStream(bos); Ex15_19_UserInfo u1 = new Ex15_19_UserInfo("JavaMan", "1234", 30); Ex15_19_UserInfo u2 = new Ex15_19_UserInfo("JavaWoMan", "1234", 26); ArrayList<Ex15_19_UserInfo> list = new ArrayList<>(); list.add(u1); list.add(u2); //직렬화한다. out.writeObject(u1); out.writeObject(u2); out.writeObject(list); out.close(); System.out.println("직렬화가 끝났습니다."); }catch (IOException e){ e.printStackTrace(); } } }
<역직렬화 예제>
이전 직렬화 예제에서 직렬화한 객체를 역질려화하는 예제이다. 이전과 반대로 inputStream을 사용했고, writeObject()대신 readObject()를 사용했다는 점을 제외하고 거의 같다. readObject()의 리턴타입이 Object이므로 원래 타입으로 형변환 해줘야 한다.!
주의해야할 점은 객체를 역직렬화 할때는 순서를 조심해야한다. 직렬화한 순서대로 처리해야한다. 따라서 직렬화할 객체가 많은 경우 ArrayList이용하는 게 좋다. (순서 고려 x)
package part_15; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Arrays; public class Ex15_21_Deserializable { public static void main(String[] args) { try{ String fileName = "UserInfo.ser"; FileInputStream fis = new FileInputStream(fileName); BufferedInputStream bis = new BufferedInputStream(fis); ObjectInputStream in = new ObjectInputStream(bis); //객체를 읽을 때는 출력한 순서와 일치해야 한다. Ex15_19_UserInfo u1 = (Ex15_19_UserInfo) in.readObject(); Ex15_19_UserInfo u2 = (Ex15_19_UserInfo) in.readObject(); ArrayList list = (ArrayList)in.readObject(); System.out.println("u1 = " + u1); System.out.println("u2 = " + u2); System.out.println("list = " + list); in.close(); }catch (Exception e){ e.printStackTrace(); } } }
📑 Java 직렬화의 장점과 단점
📌 장점
: 자바 직렬화는 자바 시스템에서 개발에 최적화되어 있다.복잡한 데이터 구조의 클래스 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화, 역직렬화가 가능하다. 또한, 데이터 타입이 자동으로 맞춰지기 때문에 역직렬화가 되면 기존 객체처럼 바로 사용이 가능하다.
📌 단점
: 역직렬화를 할 때 클래스 구조가 변경되면 문제가 발생한다. InvalidClassException발생! 또한 객체의 변수명은 같은데 객체 변수 타입이 달리져도 예외가 발생한다.
반응형'프로그래밍 언어 > JAVA' 카테고리의 다른 글
[JAVA] Object 클래스의 equals()와 hashCode() 재정의 (1) 2022.10.15 [Servlet] HttpServletRequest, HttpServletResponse 파헤치기 (1) 2022.10.11 [JAVA JUnit] assertThat의 isSameAs와 isEqaulTo 비교 분석 (1) 2022.10.01 [JAVA] static import에 대하여 ( +IntelliJ에서 사용하기) (0) 2022.09.29 [Java Junit] AssertJ 메서드 사용법 알아보기 (0) 2022.09.27