“개체 직렬화”란 무엇입니까? 몇 가지 예를 들어 설명해 주시겠습니까?
답변
직렬화는 객체를 일련의 바이트로 변환하여 객체를 영구 저장소에 쉽게 저장하거나 통신 링크를 통해 스트리밍 할 수 있습니다. 그런 다음 바이트 스트림을 역 직렬화하여 원래 객체의 복제본으로 변환 할 수 있습니다.
답변
직렬화를 객체 인스턴스를 일련의 바이트로 변환하는 프로세스라고 생각할 수 있습니다 (구현에 따라 이진일 수도 있고 아닐 수도 있음).
하나의 JVM에서 다른 JVM으로 네트워크를 통해 하나의 오브젝트 데이터를 전송하려는 경우 매우 유용합니다.
Java에서는 직렬화 메커니즘이 플랫폼에 내장되어 있지만 직렬화 가능 인터페이스를 구현하여 오브젝트를 직렬화 할 수 있도록해야합니다.
속성을 transient 로 표시하여 객체의 일부 데이터가 직렬화되는 것을 방지 할 수도 있습니다 .
마지막으로 기본 메커니즘을 무시하고 고유 한 메커니즘을 제공 할 수 있습니다. 이것은 특별한 경우에 적합 할 수 있습니다. 이를 위해 java 의 숨겨진 기능 중 하나를 사용 합니다.
직렬화되는 것은 클래스 정의가 아니라 객체 또는 내용의 “값”이라는 점에 유의해야합니다. 따라서 메소드는 직렬화되지 않습니다.
다음은 쉽게 읽을 수있는 주석이 포함 된 매우 기본적인 샘플입니다.
import java.io.*;
import java.util.*;
// This class implements "Serializable" to let the system know
// it's ok to do it. You as programmer are aware of that.
public class SerializationSample implements Serializable {
// These attributes conform the "value" of the object.
// These two will be serialized;
private String aString = "The value of that string";
private int someInteger = 0;
// But this won't since it is marked as transient.
private transient List<File> unInterestingLongLongList;
// Main method to test.
public static void main( String [] args ) throws IOException {
// Create a sample object, that contains the default values.
SerializationSample instance = new SerializationSample();
// The "ObjectOutputStream" class has the default
// definition to serialize an object.
ObjectOutputStream oos = new ObjectOutputStream(
// By using "FileOutputStream" we will
// Write it to a File in the file system
// It could have been a Socket to another
// machine, a database, an in memory array, etc.
new FileOutputStream(new File("o.ser")));
// do the magic
oos.writeObject( instance );
// close the writing.
oos.close();
}
}
이 프로그램을 실행하면 “o.ser”파일이 생성되고 어떤 일이 발생했는지 확인할 수 있습니다.
someInteger 값 을 예를 들어 Integer.MAX_VALUE 로 변경하면 출력을 비교하여 차이가 무엇인지 확인할 수 있습니다.
그 차이점을 정확하게 보여주는 스크린 샷은 다음과 같습니다.
차이점을 알 수 있습니까? 😉
Java 직렬화에는 추가 관련 필드가 있습니다. serialversionUID 이지만 이미 너무 길어서 다루기가 어렵 습니다.
답변
Java를 처음 접하는 사람들을위한 매우 높은 수준의 이해를 추가하여 6 살짜리 질문에 대답
직렬화 란 무엇입니까?
객체를 바이트로 변환
역 직렬화 란 무엇입니까?
바이트를 객체로 다시 변환 (직렬화).
직렬화는 언제 사용됩니까?
우리가 객체를 유지하고 싶을 때. JVM의 수명을 넘어서 개체가 존재하기를 원하는 경우
실제 예 :
ATM : 계정 소유자가 ATM을 통해 서버에서 돈을 인출하려고하면 인출 세부 사항과 같은 계정 소유자 정보가 직렬화되어 세부 사항이 직렬화 해제되어 조작을 수행하는 데 사용되는 서버로 전송됩니다.
Java에서 직렬화가 수행되는 방법
-
구현
java.io.Serializable
(어떤 방법을 구현하지 할 수 있도록 마커 인터페이스) 인터페이스를. -
객체 유지 :
java.io.ObjectOutputStream
하위 수준의 바이트 스트림을 감싸는 래퍼 인 필터 스트림 인 class를 사용 합니다 (파일 시스템에 객체를 쓰거나 네트워크 와이어를 통해 평평한 객체를 전송하고 다른 쪽에서 다시 작성).writeObject(<<instance>>)
-객체를 작성readObject()
-직렬화 된 객체를 읽는다
생각해 내다:
객체를 직렬화하면 객체의 클래스 파일이나 메소드가 아니라 객체의 상태 만 저장됩니다.
2 바이트 오브젝트를 직렬화하면 51 바이트 직렬화 파일이 표시됩니다.
개체의 직렬화 및 역 직렬화 방법을 단계별로 설명합니다.
답 : 51 바이트 파일로 어떻게 변환 했습니까?
- 먼저 직렬화 스트림 매직 데이터 (STREAM_MAGIC = “AC ED”및 STREAM_VERSION = JVM의 버전)를 씁니다.
- 그런 다음 인스턴스와 연관된 클래스의 메타 데이터 (클래스 길이, 클래스 이름, serialVersionUID)를 작성합니다.
- 그런 다음을 찾을 때까지 슈퍼 클래스의 메타 데이터를 재귀 적으로 작성합니다
java.lang.Object
. - 그런 다음 인스턴스와 연관된 실제 데이터로 시작합니다.
- 마지막으로 메타 데이터에서 시작하여 인스턴스와 관련된 객체의 데이터를 실제 콘텐츠에 씁니다.
편집 : 읽을 하나 더 좋은 링크 .
이것은 몇 가지 자주 묻는 질문에 대한 답변입니다.
-
클래스의 필드를 직렬화하지 않는 방법.
답변 : 임시 키워드 사용 -
자식 클래스가 직렬화되면 부모 클래스가 직렬화됩니까?
답변 : 아니요, 부모가 직렬화 가능 인터페이스 부모 필드를 확장하지 않는 경우 직렬화되지 않습니다. -
부모가 직렬화되면 자식 클래스가 직렬화됩니까?
답변 : 예. 기본적으로 자식 클래스도 직렬화됩니다. -
자식 클래스가 직렬화되는 것을 피하는 방법은 무엇입니까?
답변 : a. writeObject 및 readObject 메소드를 대체하고 throw하십시오NotSerializableException
.비. 또한 자식 클래스에서 모든 필드를 일시적으로 표시 할 수 있습니다.
- Thread, OutputStream 및 해당 서브 클래스 및 Socket과 같은 일부 시스템 레벨 클래스는 직렬화 할 수 없습니다.
답변
직렬화는 메모리에서 “라이브”개체를 가져 와서 어딘가에 저장 (예 : 메모리, 디스크) 한 후 나중에 다시 라이브 개체로 “직렬화 해제”할 수있는 형식으로 변환합니다.
답변
@OscarRyz가 제시하는 방식이 마음에 들었습니다. 비록 여기 에 @amitgupta에 의해 쓰여진 직렬화 이야기를 계속하고 있지만 .
로봇 클래스 구조를 알고 데이터를 직렬화 했음에도 불구하고 지구 과학자는 로봇을 작동시킬 수있는 데이터를 직렬화 해제 할 수 없었습니다.
Exception in thread "main" java.io.InvalidClassException:
SerializeMe; local class incompatible: stream classdesc
:
화성의 과학자들은 완전한 지불을 기다리고있었습니다. 결제가 완료되면 화성의 과학자들은 지구의 과학자들과 serialversionUID 를 공유했습니다 . 지구의 과학자는 그것을 로봇 클래스로 설정했고 모든 것이 잘되었습니다.
답변
직렬화는 Java에서 객체를 유지하는 것을 의미합니다. 객체의 상태를 저장하고 나중에 상태를 다시 빌드하려는 경우 (다른 JVM에있을 수 있음) 직렬화를 사용할 수 있습니다.
객체의 속성은 저장 만됩니다. 객체를 다시 부활 시키려면 멤버 변수 만 저장되고 멤버 함수는 저장되지 않기 때문에 클래스 파일이 있어야합니다.
예 :
ObjectInputStream oos = new ObjectInputStream(
new FileInputStream( new File("o.ser")) ) ;
SerializationSample SS = (SearializationSample) oos.readObject();
Searializable은 클래스가 직렬화 가능함을 표시하는 마커 인터페이스입니다. 마커 인터페이스는 빈 인터페이스 일 뿐이며 해당 인터페이스를 사용하면이 클래스를 직렬화 할 수 있음을 JVM에 알립니다.
답변
내 블로그에서 내 2 센트 :
다음은 Serialization에 대한 자세한 설명입니다 . (나의 블로그)
직렬화 :
직렬화는 객체의 상태를 유지하는 프로세스입니다. 일련의 바이트 형식으로 표시되고 저장됩니다. 파일에 저장할 수 있습니다. 파일에서 객체의 상태를 읽고 복원하는 과정을 역 직렬화라고합니다.
직렬화의 필요성은 무엇입니까?
현대 건축에서는 항상 객체 상태를 저장 한 다음 검색해야합니다. 예를 들어, Hibernate에서 객체를 저장하기 위해서는 Serializable 클래스를 만들어야한다. 그것은 일단 객체 상태가 바이트 형태로 저장되면 다른 시스템으로 전송되어 상태에서 읽고 클래스를 검색 할 수 있다는 것입니다. 객체 상태는 데이터베이스 또는 다른 jvm 또는 별도의 구성 요소에서 올 수 있습니다. Serialization의 도움으로 Object 상태를 검색 할 수 있습니다.
코드 예 및 설명 :
먼저 아이템 클래스를 살펴 봅시다 :
public class Item implements Serializable{
/**
* This is the Serializable class
*/
private static final long serialVersionUID = 475918891428093041L;
private Long itemId;
private String itemName;
private transient Double itemCostPrice;
public Item(Long itemId, String itemName, Double itemCostPrice) {
super();
this.itemId = itemId;
this.itemName = itemName;
this.itemCostPrice = itemCostPrice;
}
public Long getItemId() {
return itemId;
}
@Override
public String toString() {
return "Item [itemId=" + itemId + ", itemName=" + itemName + ", itemCostPrice=" + itemCostPrice + "]";
}
public void setItemId(Long itemId) {
this.itemId = itemId;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public Double getItemCostPrice() {
return itemCostPrice;
}
public void setItemCostPrice(Double itemCostPrice) {
this.itemCostPrice = itemCostPrice;
}
}
위 코드에서 Item 클래스가 Serializable을 구현 하고 있음을 알 수 있습니다 .
클래스를 직렬화 할 수있는 인터페이스입니다.
이제 serialVersionUID 라는 변수가 Long 변수로 초기화 된 것을 볼 수 있습니다 . 이 숫자는 클래스 상태 및 클래스 속성에 따라 컴파일러에서 계산됩니다. 파일에서 객체의 상태를 읽을 때 jvm이 객체의 상태를 식별하는 데 도움이되는 숫자입니다.
이를 위해 공식 Oracle Documentation을 살펴볼 수 있습니다.
직렬화 런타임은 직렬화 가능 오브젝트의 송신자 및 수신자가 직렬화와 호환되는 해당 오브젝트에 대해 클래스를로드했는지 검증하기 위해 직렬화 해제 중에 사용되는 직렬 버전 UID라고하는 버전 번호를 각 직렬화 가능 클래스와 연관시킵니다. 수신자가 해당 송신자의 클래스와 다른 serialVersionUID를 가진 객체의 클래스를로드 한 경우 역 직렬화는 InvalidClassException을 발생시킵니다. 직렬화 가능 클래스는 static, final 및 long 유형이어야하는 “serialVersionUID”라는 필드를 선언하여 고유 한 serialVersionUID를 명시 적으로 선언 할 수 있습니다. ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; 직렬화 가능 클래스가 serialVersionUID를 명시 적으로 선언하지 않으면 직렬화 런타임은 Java (TM) 객체 직렬화 스펙에 설명 된대로 클래스의 다양한 측면을 기반으로 해당 클래스의 기본 serialVersionUID 값을 계산합니다. 그러나 기본 serialVersionUID 계산은 컴파일러 구현에 따라 달라질 수있는 클래스 세부 정보에 매우 민감하므로 역 직렬화 중에 예기치 않은 InvalidClassExceptions가 발생할 수 있으므로 모든 serializable 클래스는 serialVersionUID 값을 명시 적으로 선언하는 것이 좋습니다. 따라서 다른 Java 컴파일러 구현에서 일관된 serialVersionUID 값을 보장하려면 직렬화 가능 클래스가 명시 적 serialVersionUID 값을 선언해야합니다. 또한 명시적인 serialVersionUID 선언은 가능한 경우 전용 수정자를 사용하는 것이 좋습니다.
만약 당신이 우리가 사용한 다른 키워드가 transient 인 것을 눈치 채 셨다면 .
필드를 직렬화 할 수없는 경우 일시적으로 표시해야합니다. 여기서 itemCostPrice 를 일시적으로 표시하고 파일에 쓰지 않기를 원합니다.
이제 파일에 객체의 상태를 작성하고 거기서 읽는 방법을 살펴 보겠습니다.
public class SerializationExample {
public static void main(String[] args){
serialize();
deserialize();
}
public static void serialize(){
Item item = new Item(1L,"Pen", 12.55);
System.out.println("Before Serialization" + item);
FileOutputStream fileOut;
try {
fileOut = new FileOutputStream("/tmp/item.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(item);
out.close();
fileOut.close();
System.out.println("Serialized data is saved in /tmp/item.ser");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deserialize(){
Item item;
try {
FileInputStream fileIn = new FileInputStream("/tmp/item.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
item = (Item) in.readObject();
System.out.println("Serialized data is read from /tmp/item.ser");
System.out.println("After Deserialization" + item);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
위에서 우리는 객체의 직렬화와 역 직렬화의 예를 볼 수 있습니다.
이를 위해 두 가지 클래스를 사용했습니다. 객체를 직렬화하기 위해 ObjectOutputStream을 사용했습니다. writeObject 메소드를 사용하여 파일에 오브젝트를 작성했습니다.
역 직렬화를 위해 파일에서 객체를 읽는 ObjectInputStream을 사용했습니다. readObject를 사용하여 파일에서 오브젝트 데이터를 읽습니다.
위 코드의 출력은 다음과 같습니다.
Before SerializationItem [itemId=1, itemName=Pen, itemCostPrice=12.55]
Serialized data is saved in /tmp/item.ser
After DeserializationItem [itemId=1, itemName=Pen, itemCostPrice=null]
공지 사항 것을 itemCostPrice 직렬화 된 객체는 널 (null) 이 작성되지 않았습니다으로가.
이 기사의 1 부에서 Java 직렬화의 기본 사항에 대해 이미 논의했다.
이제 자세히 살펴보고 작동 방식을 살펴 보겠습니다.
먼저 serialversionuid 부터 시작하겠습니다 .
의 serialVersionUID가 직렬화 가능 클래스의 버전 컨트롤로 사용됩니다.
serialVersionUID를 명시 적으로 선언하지 않으면 JVM은 Serializable 클래스의 다양한 속성에 따라 자동으로이를 수행합니다.
serialversionuid 계산의 Java 알고리즘 (자세한 내용은 여기를 참조하십시오)
- 클래스 이름
- 클래스 수정자는 32 비트 정수로 작성되었습니다.
- 이름별로 정렬 된 각 인터페이스의 이름입니다.
- 클래스 이름별로 정렬 된 클래스의 각 필드 (개인 정적 및 개인 임시 필드 제외 : 필드 이름, 필드의 수정 자, 32 비트 정수로 기록 된 필드의 설명자)
- 클래스 이니셜 라이저가 존재하면 다음을 작성하십시오. 메소드 이름.
- 메소드의 수정 자 java.lang.reflect.Modifier.STATIC은 32 비트 정수로 작성됩니다.
- 메소드의 설명자 () V.
- 메소드 이름 및 서명별로 정렬 된 각각의 개인용이 아닌 생성자에 대해 다음과 같이하십시오. 메소드의 이름. 메소드의 수정자는 32 비트 정수로 작성됩니다. 메소드의 설명자
- 메소드 이름 및 서명별로 정렬 된 각각의 비 개인 메소드에 대해 : 메소드 이름. 메소드의 수정자는 32 비트 정수로 작성됩니다. 메소드의 설명자
- SHA-1 알고리즘은 DataOutputStream에 의해 생성 된 바이트 스트림에서 실행되며 5 개의 32 비트 값 sha [0..4]를 생성합니다. 해시 값은 SHA-1 메시지 요약의 첫 번째 및 두 번째 32 비트 값으로 구성됩니다. 메시지 다이제스트의 결과 인 5 개의 32 비트 단어 H0 H1 H2 H3 H4가 sha라는 5 개의 int 값의 배열에 있으면 해시 값은 다음과 같이 계산됩니다.
long hash = ((sha[0] >>> 24) & 0xFF) |
> ((sha[0] >>> 16) & 0xFF) << 8 |
> ((sha[0] >>> 8) & 0xFF) << 16 |
> ((sha[0] >>> 0) & 0xFF) << 24 |
> ((sha[1] >>> 24) & 0xFF) << 32 |
> ((sha[1] >>> 16) & 0xFF) << 40 |
> ((sha[1] >>> 8) & 0xFF) << 48 |
> ((sha[1] >>> 0) & 0xFF) << 56;
자바의 직렬화 알고리즘
객체를 직렬화하는 알고리즘은 다음과 같습니다.
1. 인스턴스와 관련된 클래스의 메타 데이터를 작성합니다.
2. java.lang.object를 찾을 때까지 수퍼 클래스의 설명을 재귀 적으로 작성합니다 .
3. 메타 데이터 정보 쓰기가 끝나면 인스턴스와 연결된 실제 데이터로 시작합니다. 그러나 이번에는 최상위 슈퍼 클래스에서 시작합니다.
4. 최소 수퍼 클래스에서 가장 파생 된 클래스에 이르기까지 인스턴스와 관련된 데이터를 재귀 적으로 씁니다.
명심해야 할 것들 :
-
클래스의 정적 필드는 직렬화 할 수 없습니다.
public class A implements Serializable{ String s; static String staticString = "I won't be serializable"; }
-
읽기 버전에서 serialversionuid가 다른 경우
InvalidClassException
예외 가 발생합니다. -
클래스가 직렬화 가능을 구현하면 모든 하위 클래스도 직렬화 가능합니다.
public class A implements Serializable {....}; public class B extends A{...} //also Serializable
-
클래스에 다른 클래스에 대한 참조가있는 경우 모든 참조는 직렬화 가능해야합니다. 그렇지 않으면 직렬화 프로세스가 수행되지 않습니다. 이러한 경우 런타임에 NotSerializableException 이 발생합니다.
예 :
public class B{
String s,
A a; // class A needs to be serializable i.e. it must implement Serializable
}