이 프로세스는 네트워크를 통해 구현할 수도 있습니다. 먼저 Windows 시스템에서 개체를 생성하고 직렬화한 다음 네트워크를 통해 Unix 시스템으로 보낸 다음 그곳에서 정확하게 다시 "어셈블"할 수 있습니다. 그 중 RMI, Socket, JMS, EJB 등이 Java 객체를 서로 주고받을 수 있는 이유는 당연히 객체 직렬화 메커니즘 때문입니다.
Java 객체 직렬화 메커니즘은 일반적으로 두 가지 용도로 사용됩니다.
Java의 JavaBeans: Bean의 상태 정보는 일반적으로 디자인 타임에 구성되며, 디자인 시 이 상태 정보를 복원하려면 Bean의 상태 정보를 저장해야 합니다. 프로그램이 실행 중이면 객체의 상태를 파일에 저장해야 하며, 그런 다음 객체 상태를 읽어 객체를 재구성하고 프로그램 상태를 복원할 수 있습니다.
RMI를 사용하면 원격 시스템의 개체를 로컬 시스템에 있는 것처럼 작동할 수 있으며, 소켓을 사용하여 네트워크를 통해 개체를 전송하는 프로그램의 경우 직렬화 메커니즘을 구현해야 합니다.
클래스가 Java.io.Serialized 인터페이스를 구현하도록 하여 클래스를 직렬화할 수 있습니다. 이 인터페이스는 마커 인터페이스입니다. 즉, 인터페이스는 클래스를 구현하기 위해 어떤 메서드도 구현할 필요가 없습니다. 주로 JVM(Java Virtual Machine)에 객체를 직렬화해야 함을 알리는 데 사용됩니다.
이를 위해 몇 가지 명확히 해야 할 사항이 있습니다.
모든 클래스를 직렬화할 수 있는 것은 아닙니다. cmd에서 serialver Java.net.Socket을 입력하여 소켓이 있는지 확인합니다. 정보는 실제로 소켓을 직렬화할 수 없습니다.
Java에는 문자열, 벡터 등과 같은 직렬화 가능 인터페이스를 구현한 많은 기본 클래스가 있습니다. 그러나 예를 들어 해시테이블은 직렬화 가능 인터페이스를 구현하지 않습니다.
객체를 스트림으로 읽거나 쓰는 데는 ObjectOutputStream과 ObjectInputStream이라는 두 가지 주요 클래스가 있습니다. ObjectOutputStream은 출력 스트림에 객체를 쓰기 위한 writeObject 메서드를 제공하고, ObjectInputStream은 입력 스트림에서 객체를 읽기 위한 readObject 메서드를 제공합니다. 이러한 메서드를 사용하는 개체는 이미 직렬화되어 있어야 합니다. 즉, 직렬화 가능 인터페이스가 구현되어 있어야 합니다. writeobject를 해시 테이블 개체로 사용하려는 경우 예외가 발생합니다.
직렬화 프로세스는 객체를 바이트 스트림에 쓰고 바이트 스트림에서 객체를 읽는 것입니다. 객체 상태를 바이트 스트림으로 변환한 후 Java.io 패키지의 다양한 바이트 스트림 클래스를 사용하여 이를 파일에 저장하거나, 다른 스레드로 파이프하거나, 네트워크 연결을 통해 객체 데이터를 다른 호스트로 보낼 수 있습니다. 객체 직렬화 기능은 매우 간단하고 강력하며 RMI, 소켓, JMS 및 EJB에 적용됩니다. 객체 직렬화 문제는 네트워크 프로그래밍에서 가장 흥미로운 주제는 아니지만 매우 중요하고 많은 실질적인 의미를 갖습니다.
객체 직렬화는 분산 객체를 구현할 수 있습니다. 주요 응용 프로그램은 다음과 같습니다. RMI는 로컬 시스템에서 개체를 실행할 때와 마찬가지로 개체 직렬화를 사용하여 원격 호스트에서 서비스를 실행합니다.
Java 객체 직렬화는 한 객체의 데이터를 유지할 뿐만 아니라 객체가 참조하는 각 객체의 데이터를 재귀적으로 저장합니다. 전체 개체 계층 구조는 바이트 스트림에 기록될 수 있으며, 이는 파일에 저장되거나 네트워크 연결을 통해 전달될 수 있습니다. 개체 직렬화를 사용하여 개체의 "전체 복사", 즉 개체 자체와 참조된 개체 자체를 복사할 수 있습니다. 개체를 직렬화하면 전체 개체 시퀀스가 생성될 수 있습니다.
Java 직렬화는 비교적 간단하며 일반적으로 객체 상태를 저장하고 복원하기 위해 사용자 정의 코드를 작성할 필요가 없습니다. Java.io.Serialized 인터페이스를 구현하는 클래스 객체는 클래스에 코드를 추가하지 않고도 바이트 스트림으로 변환하거나 복원할 수 있습니다. 드문 경우지만 개체 상태를 저장하거나 복원하는 데 사용자 지정 코드가 필요합니다. 참고: 모든 클래스를 직렬화할 수는 없으며 일부 클래스는 직렬화할 수 없습니다. 예를 들어 스레드와 관련된 클래스는 특정 JVM과 매우 복잡한 관계를 갖습니다.
직렬화 메커니즘:
직렬화는 직렬화와 역직렬화의 두 부분으로 나뉩니다. 직렬화는 프로세스의 첫 번째 부분으로, 데이터를 바이트 스트림으로 나누어 파일에 저장하거나 네트워크를 통해 전송할 수 있습니다. 역직렬화는 바이트 스트림을 열고 객체를 재구성하는 것입니다. 객체 직렬화는 기본 데이터 유형을 바이트 표현으로 변환할 뿐만 아니라 때로는 데이터를 복원하기도 합니다. 데이터를 복원하려면 복원된 데이터의 개체 인스턴스가 필요합니다. ObjectOutputStream의 직렬화 프로세스는 객체 유형 및 버전 정보를 포함하여 바이트 스트림과 연결됩니다. 역직렬화할 때 JVM은 헤더 정보를 사용하여 객체 인스턴스를 생성한 다음 객체 바이트 스트림의 데이터를 객체 데이터 멤버에 복사합니다. 아래에서는 두 부분으로 나누어 설명하겠습니다.
객체 스트림 처리: (직렬화 프로세스 및 역직렬화 프로세스)
Java.io 패키지에는 객체 직렬화를 위한 두 가지 클래스가 있습니다. ObjectOutputStream은 바이트 스트림에 객체를 쓰는 일을 담당하고, ObjectInputStream은 바이트 스트림에서 객체를 재구성합니다.
먼저 ObjectOutputStream 클래스를 이해해 봅시다. ObjectOutputStream 클래스는 DataOutput 인터페이스를 확장합니다.
writeObject() 메소드는 가장 중요한 메소드이며 객체 직렬화에 사용됩니다. 객체에 다른 객체에 대한 참조가 포함되어 있는 경우 writeObject() 메서드는 해당 객체를 재귀적으로 직렬화합니다. 각 ObjectOutputStream은 동일한 개체의 여러 복사본을 보내는 것을 방지하기 위해 직렬화된 개체 참조 테이블을 유지 관리합니다. (중요함) writeObject()는 상호 참조된 객체의 전체 집합을 직렬화할 수 있으므로 동일한 ObjectOutputStream 인스턴스가 실수로 동일한 객체를 직렬화하도록 요청될 수 있습니다. 이때 객체 바이트 스트림을 다시 쓰는 대신 역참조 직렬화가 수행됩니다.
이제 예제를 통해 ObjectOutputStream 클래스에 대해 알아 보겠습니다.
// 序列化 today's date 到一个文件中. FileOutputStream f = new FileOutputStream ("tmp" ); ObjectOutputStream s = new ObjectOutputStream (f); s.writeObject("Today" ); s.writeObject(new Date ()); s.flush();
이제 ObjectInputStream 클래스를 이해해 보겠습니다. ObjectOutputStream과 유사합니다. DataInput 인터페이스를 확장합니다. ObjectInputStream의 메소드는 Java 기본 데이터 유형을 읽기 위해 DataInputStream의 공용 메소드를 미러링합니다. readObject() 메서드는 바이트 스트림에서 객체를 역직렬화합니다. readObject() 메서드가 호출될 때마다 스트림의 다음 객체가 반환됩니다. 객체 바이트 스트림은 클래스의 바이트코드를 전송하지 않지만 클래스 이름과 서명을 포함합니다. readObject()가 객체를 수신하면 JVM은 헤더에 지정된 클래스를 로드합니다. 이 클래스를 찾을 수 없으면 readObject()가 ClassNotFoundException을 발생시킵니다. 객체 데이터와 바이트코드를 전송해야 하는 경우 RMI 프레임워크를 사용할 수 있습니다. ObjectInputStream의 나머지 메서드는 역직렬화 프로세스를 사용자 지정하는 데 사용됩니다.
예는 다음과 같습니다.
//从文件中反序列化 string 对象和 date 对象 FileInputStream in = new FileInputStream ("tmp" ); ObjectInputStream s = new ObjectInputStream (in); String today = (String )s.readObject(); Date date = (Date )s.readObject();사용자 정의 직렬화 프로세스:
예: 매우 간단한 직렬화 클래스.
public class simpleSerializableClass implements Serializable { String sToday="Today:" ; transient Date dtToday=new Date (); }
직렬화할 때 임시 또는 정적으로 선언된 멤버를 제외하고 클래스의 모든 데이터 멤버는 직렬화 가능해야 합니다. 변수를 임시 변수로 선언하면 JVM에 변수 직렬화를 담당하게 됩니다. 데이터 멤버가 임시로 선언된 후에는 직렬화 프로세스가 이를 개체 바이트 스트림에 추가할 수 없습니다. 임시 데이터 멤버에서 전송된 데이터가 없습니다. 나중에 데이터가 역직렬화되면 데이터 멤버를 재구성해야 하지만(클래스 정의의 일부이기 때문에) 이 데이터 멤버는 스트림에 데이터를 쓰지 않기 때문에 데이터가 포함되어 있지 않습니다. 객체 스트림은 정적 또는 임시 객체를 직렬화하지 않는다는 점을 기억하세요. 우리 클래스는 writeObject() 및 readObject() 메서드를 사용하여 이러한 데이터 멤버를 처리합니다. writeObject() 및 readObject() 메서드를 사용할 때 이러한 데이터 멤버를 작성된 순서대로 읽어야 합니다.
사용자 정의 직렬화를 사용하는 방법에 대한 코드의 일부는 다음과 같습니다
//重写writeObject()方法以便处理transient的成员。 public void writeObject(ObjectOutputStream outputStream) throws IOException { outputStream.defaultWriteObject();//使定制的writeObject()方法可以 //利用自动序列化中内置的逻辑。 outputStream.writeObject(oSocket.getInetAddress()); outputStream.writeInt(oSocket.getPort()); } //重写readObject()方法以便接收transient的成员。 private void readObject(ObjectInputStream inputStream) throws IOException , ClassNotFoundException { inputStream.defaultReadObject();//defaultReadObject()补充自动序列化 InetAddress oAddress=(InetAddress )inputStream.readObject(); int iPort =inputStream.readInt(); oSocket = new Socket (oAddress,iPort); iID=getID(); dtToday =new Date (); }
직렬화 프로세스를 완전히 사용자 정의합니다.
클래스가 자체 직렬화를 전적으로 담당하려면 대신 외부화 가능 인터페이스를 구현하세요. 직렬화 가능 인터페이스의 외부화 가능 인터페이스 정의에는 writeExternal() 및 readExternal()의 두 가지 메소드가 포함되어 있습니다. 이러한 메소드는 객체 데이터 멤버가 바이트 스트림에 기록되는 방식을 제어하는 데 사용할 수 있습니다. 클래스가 외부화 가능을 구현하면 헤더가 객체 스트림에 기록되고 클래스는 데이터 멤버를 직렬화하고 복원하는 일을 전적으로 담당합니다. 헤더의 경우 자동 직렬화가 전혀 없습니다. 여기서 주목해야 할 것이 있습니다. 외부화 가능 인터페이스를 구현하기 위해 클래스를 선언하면 심각한 보안 위험이 발생합니다. writeExternal() 및 readExternal() 메서드는 공개로 선언되며, 악성 클래스는 이러한 메서드를 사용하여 객체 데이터를 읽고 쓸 수 있습니다. 개체에 민감한 정보가 포함되어 있는 경우 각별히 주의하십시오. 여기에는 보안 소켓 사용 또는 전체 바이트 스트림 암호화가 포함됩니다. 지금까지 직렬화의 기본을 배웠습니다.
Java 튜토리얼 및 관련 기사에서 객체 직렬화의 기본 예에 대한 자세한 설명을 보려면 PHP 중국어 웹사이트를 주목하세요!