1. 为什么要序列化
Java进行序列化的主要原因在于需要将对象的状态信息转换为可以存储或传输的形式,以便于在不同平台、不同时间之间共享和交换数据。用途举例如下:
1.1. 持久化存储
序列化可以将对象转换为字节流,然后保存到磁盘文件、数据库或缓存中,实现数据的持久化。这样,即使系统重启或关闭,也可以从序列化的文件中读取对象,并恢复到内存中继续使用。
1.2.网络传输
在网络通信中,数据只能以二进制的形式进行传输。因此,当需要将对象从一个节点发送到另一个节点时,必须先将其序列化为二进制数据。接收端在收到数据后,可以反序列化为Java对象,从而实现对象在网络中的传输。
1.3.进程间通信
在多进程或分布式系统中,不同进程或节点之间经常需要传递数据。通过序列化,可以将对象转换为字节流,并通过进程间通信(IPC)机制(如管道、套接字等)进行传递,从而简化数据传递的过程。
1.4. 对象复制
有时候需要复制对象,通过序列化和反序列化可以快速实现对象的深拷贝,即创建一个与原始对象完全相同的新对象。
例:如下的这样一个对象。
public class MyObject implements Serializable{
private String name;
private String address;
public MyObject(String name,String address){
this.name = name;
this.address = address;
}
}
public class ObjectOutputStreamTest {
public static void main(String[] args){
MyObject myObject = new MyObject("Zhang San","china");
}
}
数据以对象的形式存在内存中,但是当我们需要以网络形式传输对象,或者是固化存入磁盘。就需要将对象转化为连续的二进制流,再以同样的方式在接收方反序列化为相同的对象。方便传输或固化。
如上的对象序列化后的字符和二进制形式表现如下:
2. 序列化
序列化通常使用ObjectOutputStream流进行,ObjectOutputStream本身没有内置的buffer。它是一个用于将对象序列化为字节流并写入到输出流中的类。当你使用ObjectOutputStream时,通常需要其它文件流,网络流或内存流来接收存储序列化后的流数据。
public class ObjectOutputStreamTest {
public static void main(String[] args){
MyObject myObject = new MyObject("nameExample",23,"china","180");
try {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\**\\Downloads\\test.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(myObject);
objectOutputStream.close();
fileOutputStream.close();
System.out.println("Object has been serialized and written to file.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
class MyObject implements Serializable{
private String name;
private transient int age;
private String address;
public static String height;
public MyObject(String name,int age,String address,String height){
this.name = name;
this.age = age;
this.address = address;
this.height = height;
}
}
3.反序列化
public class ObjectInputStreamTest {
public static void main(String[] args) {
try {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\**\\Downloads\\test.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
MyObject myObject = (MyObject) objectInputStream.readObject();
System.out.println(myObject.toString());
objectInputStream.close();
fileInputStream.close();
} catch (IOException | ClassNotFoundException e){
e.printStackTrace();
}
}
}
反序列化后的对象结果:
我们可以看到序列化时的static属性height,明显没有了,而transient修饰的属性age的值也没有传输过来。故而:
- 静态的变量在序列化过程中会被忽略,而不会被写入到序列化流中。
- 被transient修饰的变量在序列化过程中会被忽略,而不会被写入到序列化流中。所以我们在序列化某对象时,如果有某属性不需要此加入序列化如密码等,可以用此关键字修饰用以屏蔽某属性的序列化。
4. 序列化对象需要实现 java.io.Serializable 接口
如果我们用以序列化的如上对象MyObject 不去implements Serializable方法,序列化的过程中会报NotSerializableException异常:
我们查看Serializable方法。可以看到它没有任何接口方法。所以Serializable 接口在 Java 中只是一个标记接口(marker interface),它本身并没有定义任何方法。标记接口的作用是提供一种机制,用于给类添加元数据,以指示该类应该被序列化。
查看底层代码我们可以看见它的逻辑如下,因为凡是没有实现Serializable类或其实现其子类,或继承其实现类的普通类都会报该异常。