CodeGym /Java Blog /무작위의 /Java에서 직렬화와 역직렬화의 차이점은 무엇입니까?
John Squirrels
레벨 41
San Francisco

Java에서 직렬화와 역직렬화의 차이점은 무엇입니까?

무작위의 그룹에 게시되었습니다
안녕! 오늘 수업에서는 Java의 직렬화 및 역직렬화에 대해 이야기합니다. 간단한 예부터 시작하겠습니다. 컴퓨터 게임을 만들었다고 가정해 봅시다. 90년대에 자랐고 그 시대의 게임 콘솔을 기억한다면 아마도 오늘날 우리가 당연하게 여기는 것, 즉 게임을 저장하고 로드하는 기능이 부족했다는 것을 알고 있을 것입니다 :) 그렇지 않다면 상상해보세요! Java에서 직렬화와 역직렬화의 차이점은 무엇입니까?  - 1 오늘날 이러한 능력이 없는 게임은 파멸될까 두렵습니다! 어쨌든 게임을 "저장"하고 "로드"한다는 것은 무엇을 의미합니까? 글쎄, 우리는 일반적인 의미를 이해합니다. 우리는 우리가 중단한 곳에서 게임을 계속하고 싶습니다. 이를 위해 일종의 "체크 포인트"를 만든 다음 게임을 로드하는 데 사용합니다. 그러나 이것은 캐주얼 게이머가 아닌 프로그래머에게 무엇을 의미합니까? 답은 간단합니다 .. Strategium에서 스페인으로 플레이하고 있다고 가정해 보겠습니다. 게임에는 상태가 있습니다. 누가 어떤 영토를 소유하고 있는지, 누가 얼마나 많은 자원을 가지고 있는지, 누가 누구와 동맹을 맺고 있는지, 누가 누구와 전쟁 중인지 등등. 나중에 복원하고 게임을 계속하려면 프로그램의 상태인 이 정보를 어떻게든 저장해야 합니다. 이것이 바로 직렬화역현실화가 필요한 이유입니다. 직렬화는 개체의 상태를 일련의 바이트로 저장하는 프로세스입니다. 역직렬화이러한 바이트에서 개체를 복원하는 프로세스입니다. 모든 Java 객체는 바이트 시퀀스로 변환될 수 있습니다. 그게 왜 필요할까요? 우리는 프로그램이 그 자체로는 존재하지 않는다고 여러 번 말했습니다. 대부분의 경우 다른 프로그램과 상호 작용하고 데이터를 교환하는 등의 작업을 수행합니다. 바이트 시퀀스는 편리하고 효율적인 형식입니다. 예를 들어 SavedGame객체를 일련의 바이트로 변환하고 이 바이트를 네트워크를 통해 다른 컴퓨터로 보낸 다음 두 번째 컴퓨터에서 이 바이트를 다시 Java 객체로 변환할 수 있습니다! 어렵게 들리죠? 그리고 이 프로세스를 구현하는 것은 고통처럼 보입니다./다행히도 그렇지 않습니다! :) 자바에서는Serializable인터페이스는 직렬화 프로세스를 담당합니다. 이 인터페이스는 매우 간단합니다. 사용하기 위해 단일 메서드를 구현할 필요가 없습니다! 게임 절약 클래스는 다음과 같이 간단합니다.

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private static final long serialVersionUID = 1L;

   private String[] territoriesInfo;
   private String[] resourcesInfo;
   private String[] diplomacyInfo;

   public SavedGame(String[] territoriesInfo, String[] resourcesInfo, String[] diplomacyInfo){
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public String[] getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(String[] territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public String[] getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(String[] resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

   public String[] getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(String[] diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + Arrays.toString(territoriesInfo) +
               ", resourcesInfo=" + Arrays.toString(resourcesInfo) +
               ", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
               '}';
   }
}
세 개의 배열은 영토, 자원 및 외교에 대한 정보를 담당합니다. Serializable 인터페이스는 JVM(Java Virtual Machine)에 " 모든 것이 정상입니다. 필요한 경우 이 클래스의 개체를 직렬화할 수 있습니다 . "라고 알려줍니다. 단일 인터페이스가 없는 인터페이스는 이상해 보입니다./ 왜 필요한가요? 이 질문에 대한 대답은 위에서 볼 수 있습니다. JVM(Java Virtual Machine)에 필요한 정보를 제공하는 역할만 합니다. 이전 수업 중 하나에서 마커 인터페이스에 대해 간략하게 언급했습니다 . 이들은 미래에 Java 머신에 유용할 추가 정보로 클래스를 간단히 표시하는 특별한 정보 인터페이스입니다. 구현해야 하는 메서드가 없습니다.Serializable인터페이스 중 하나입니다. private static final long serialVersionUID또 다른 중요한 점: 클래스에서 정의한 변수가 필요한 이유는 무엇입니까 ? 왜 필요한가요? 이 필드에는 직렬화된 클래스의 버전에 대한 고유 식별자가 포함됩니다 . 인터페이스 를 구현하는 모든 클래스 에는 식별자가 Serializable있습니다 version. 클래스의 내용(필드, 선언 순서, 메서드 등)을 기반으로 계산됩니다. 클래스의 필드 유형 및/또는 필드 수를 변경하면 버전 식별자가 즉시 변경됩니다. . serialVersionUID클래스가 직렬화될 때도 작성됩니다. 역직렬화를 시도할 때, 즉 바이트 집합에서 객체를 복원하려고 할 때 연관된 값을 다음 serialVersionUID값과 비교합니다.serialVersionUID우리 프로그램의 수업을 위해. 값이 일치하지 않으면 java.io. InvalidClassException이 발생합니다. 아래에서 이에 대한 예를 살펴보겠습니다. 이를 방지하기 위해 클래스에서 버전 식별자를 수동으로 설정하기만 하면 됩니다. 우리의 경우에는 단순히 1과 같을 것입니다(하지만 원하는 다른 숫자로 대체할 수 있습니다). 이제 개체를 직렬화하고 어떤 일이 발생하는지 확인할 시간입니다 SavedGame!

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       String[] territoryInfo = {"Spain has 6 provinces", "Russia has 10 provinces", "France has 8 provinces"};
       String[] resourcesInfo = {"Spain has 100 gold", "Russia has 80 gold", "France has 90 gold"};
       String[] diplomacyInfo = {"France is at war with Russia, Spain has taken a neutral position"};

       SavedGame savedGame = new SavedGame(territoryInfo, resourcesInfo, diplomacyInfo);

       // Create 2 streams to serialize the object and save it to a file
       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

       // Save the game to a file
       objectOutputStream.writeObject(savedGame);

       // Close the stream and free resources
       objectOutputStream.close();
   }
}
보시다시피 우리는 2개의 스트림을 생성했습니다: FileOutputStreamObjectOutputStream. 첫 번째는 파일에 데이터를 쓸 수 있고 두 번째는 개체를 바이트로 변환합니다. 예를 들어 이전 수업에서 유사한 "중첩" 구성을 이미 보았으므로 new BufferedReader(new InputStreamReader(...))겁내지 않아도 됩니다 :) 두 스트림의 "체인"을 생성하여 두 작업을 모두 수행합니다. 개체를 SavedGame집합으로 변환합니다. 메서드를 사용하여 파일에 저장합니다 writeObject(). 그건 그렇고, 우리는 우리가 얻은 것을 보지도 않았습니다! 이제 파일을 볼 시간입니다! *참고: 미리 파일을 만들 필요는 없습니다. 해당 이름의 파일이 존재하지 않으면 자동으로 생성됩니다.* 내용은 다음과 같습니다!

¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;­ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
어-오 :( 우리 프로그램이 작동하지 않는 것 같습니다 :( 사실, 작동했습니다. 파일에 단순한 객체나 텍스트가 아닌 바이트 세트를 보낸 것을 기억하십니까? 음, 이것이 바로 바이트 집합은 다음과 같습니다 :) 이것은 저장된 게임입니다! 원래 개체를 복원하려면(즉, 중단한 게임을 시작하고 계속하려면 역직렬화 과정이 필요합니다. 여기에서 우리의 모습을 볼 수 있습니다 . 사례:

import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);
   }
}
결과는 다음과 같습니다!

SavedGame{territoriesInfo=["Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces], resourcesInfo=[Spain has 100 gold, Russia has 80 gold, France has 90 gold], diplomacyInfo=[France is at war with Russia, Spain has taken a neutral position]}
훌륭한! 먼저 게임 상태를 파일에 저장한 다음 파일에서 복원했습니다. 이제 우리 클래스의 버전 식별자 없이 동일한 작업을 수행해 봅시다 SavedGame. 우리는 두 클래스를 모두 다시 작성하지 않을 것입니다. 그들의 코드는 동일하게 유지되지만 클래스 private static final long serialVersionUID에서 제거할 것입니다 SavedGame. 다음은 직렬화 후 개체입니다.

¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;­ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
하지만 역직렬화를 시도할 때 어떤 일이 발생하는지 살펴보십시오.

InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
이것은 위에서 언급한 바로 그 예외입니다. 그건 그렇고, 우리는 중요한 것을 놓쳤습니다. 문자열과 프리미티브가 쉽게 직렬화될 수 있다는 것은 이치에 맞습니다. Java에는 아마도 이를 수행하기 위한 일종의 내장 메커니즘이 있을 것입니다. serializable그러나 우리 클래스에 프리미티브가 아닌 다른 객체에 대한 참조가 있는 필드가 있다면 어떻게 될까요 ? TerritoriesInfo예를 들어 별도의 , ResourcesInfoDiplomacyInfo클래스를 만들어 우리 클래스와 함께 작동하도록 합시다 SavedGame.

public class TerritoriesInfo {
  
   private String info;

   public TerritoriesInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "TerritoriesInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class ResourcesInfo {

   private String info;

   public ResourcesInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "ResourcesInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class DiplomacyInfo {

   private String info;

   public DiplomacyInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "DiplomacyInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}
그리고 이제 질문이 생깁니다. 변경된 클래스를 직렬화하려면 이 모든 클래스가 있어야 합니까 ?SerializableSavedGame

import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private TerritoriesInfo territoriesInfo;
   private ResourcesInfo resourcesInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public TerritoriesInfo getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(TerritoriesInfo territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public ResourcesInfo getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(ResourcesInfo resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(DiplomacyInfo diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + territoriesInfo +
               ", resourcesInfo=" + resourcesInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
글쎄, 그것을 테스트하자! 모든 것을 그대로 두고 SavedGame개체를 직렬화해 보겠습니다.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoriesInfo, resourcesInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
결과:

Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
작동하지 않았습니다! 기본적으로 이것이 우리 질문에 대한 답입니다. 개체가 직렬화되면 해당 인스턴스 변수가 참조하는 모든 개체가 직렬화됩니다. 그리고 해당 객체가 다른 객체도 참조하는 경우 직렬화됩니다. 그리고 무한히 계속됩니다. 이 체인의 모든 클래스는 이어야 합니다Serializable . 그렇지 않으면 직렬화할 수 없으며 예외가 발생합니다. 그건 그렇고, 이것은 앞으로 문제를 일으킬 수 있습니다. 예를 들어 직렬화할 때 클래스의 일부가 필요하지 않은 경우 어떻게 해야 합니까? 또는 예를 들어 TerritoryInfo클래스가 일부 타사 라이브러리의 일부로 제공되는 경우 어떻게 됩니까? 그리고 더 나아가 그것이 아니라고 가정 Serializable하고 따라서 우리는 그것을 바꿀 수 없습니다. TerritoryInfo필드를 추가할 수 없다는 것이 밝혀졌습니다.SavedGame그렇게 하면 전체 SavedGame클래스를 직렬화할 수 없게 되기 때문입니다! 그게 문제야 :/ Java에서 직렬화와 역직렬화의 차이점은 무엇입니까?  - 2Java에서 이런 종류의 문제는 키워드를 사용하여 해결됩니다 transient. 클래스의 필드에 이 키워드를 추가하면 해당 필드가 직렬화되지 않습니다. 클래스의 인스턴스 필드 중 하나를 일시적으로 만들어 보겠습니다 SavedGame. 그런 다음 하나의 개체를 직렬화하고 복원합니다.

import java.io.Serializable;

public class SavedGame implements Serializable {

   private transient TerritoriesInfo territoriesInfo;
   private ResourcesInfo resourcesInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   // ...getters, setters, toString()
}



import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       SavedGame savedGame = new SavedGame(territoriesInfo, resourcesInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}


import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);

       objectInputStream.close();


   }
}
결과는 다음과 같습니다.

SavedGame{territoriesInfo=null, resourcesInfo=ResourcesInfo{info='Spain has 100 gold, Russia has 80 gold, France has 90 gold'}, diplomacyInfo=DiplomacyInfo{info='France is at war with Russia, Spain has taken a neutral position'}}
또한 필드에 할당되는 값에 대한 질문에 대한 답변을 얻었습니다 transient. 기본값이 할당됩니다. 개체의 경우 입니다 null. 몇 분의 여유 시간이 있을 때 직렬화에 대한 이 훌륭한 기사를 읽을 수 있습니다 . Externalizable또한 다음 강의에서 다룰 인터페이스에 대해서도 언급합니다 . 또한 "Head-First Java"라는 책에도 이 주제에 대한 장이 있습니다. 관심을 가져주세요 :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION